#!/usr/bin/env lua -- Deterministic OpenSSH Ed25519 key generator local monocypher = require("monocypher") local function die(message) if message then io.stderr:write("Error: " .. message .. '\n') else io.stderr:write("Usage: ./ssh-keygen.lua [counter]\n") end os.exit(1, true) end local function b64_enc(data) -- Copyright 2009 Alex Kloss return ((data:gsub('.', function (x) local r, b = '', x:byte() for i=8, 1, -1 do r = r .. (b%2^i - b%2^(i-1) > 0 and '1' or '0') end return r; end) .. "0000"):gsub("%d%d%d?%d?%d?%d?", function (x) if (#x < 6) then return '' end local c = 0 for i=1, 6 do c = c + (x:sub(i, i) == '1' and 2^(6-i) or 0) end return string.sub("ABCDEFGHIJKLMNOPQRSTUVWXYZ" .. "abcdefghijklmnopqrstuvwxyz0123456789+/", c+1, c+1) end) .. ({ '', "==", '=' })[#data%3 + 1]) end local salt = monocypher.blake2b((arg[1] or die()) .. (arg[2] or '1'), 16) io.stderr:write("Password: ") local password = io.read() local sk = monocypher.argon2i(password, salt, 32, 128000, 8) local _, pk = monocypher.ed25519_sign('', sk) -- https://dnaeon.github.io/openssh-private-key-binary-format/ local tail = "\0\0\0\0\0\0\0\0\0\0\0\vssh-ed25519\0\0\0 " .. pk .. "\0\0\0\064" .. sk .. pk .. "\0\0\0" .. string.char(#arg[1]) .. arg[1] local i = 1 while #tail % 8 ~= 0 do tail = tail .. string.char(i) i = i + 1 end local b64 = b64_enc("openssh-key-v1\0\0\0\0\004none\0\0\0\004none\0\0\0\0\0" .. "\0\0\001\0\0\0\051\0\0\0\vssh-ed25519\0\0\0 " .. pk .. "\0\0\0" .. string.char(#tail) .. tail) print("-----BEGIN OPENSSH PRIVATE KEY-----") print(b64:sub(1, 70)) print(b64:sub(71, 140)) print(b64:sub(141, 210)) print(b64:sub(211, 280)) print(b64:sub(281)) print("-----END OPENSSH PRIVATE KEY-----")