SSH Key Generator — Ruby source
Generate Ed25519 or RSA SSH key pairs in your browser. Copy or download the public and private keys. No server involved.
This is the Ruby implementation — the same logic the interactive tool runs, in a shareable, citable form.
# SSH Key Generator — Ed25519 / RSA SSH key pair generation via OpenSSL.
#
# Language: Ruby (3.1+, standard library only)
# Source: CosmoDev polyglot showcase port of the SSH Key Generator tool,
# ported from src/lib/ssh-keygen.ts (the canonical TypeScript
# implementation).
# License: display source — part of CosmoDev's polyglot tool pages.
#
# Formats produced:
# - Ed25519 public key : `ssh-ed25519 <base64(blob)> <comment>` where blob is
# the OpenSSH wire format (string "ssh-ed25519" + string 32-byte key).
# - Ed25519 private key: `-----BEGIN OPENSSH PRIVATE KEY-----` PEM containing
# the unencrypted "openssh-key-v1" structure (cipher "none", kdf "none").
# - RSA public key : `ssh-rsa <base64(blob)> <comment>` where blob is
# string "ssh-rsa" + mpint e + mpint n.
# - RSA private key : PKCS#8 PEM (`-----BEGIN PRIVATE KEY-----`).
# - Fingerprint : SHA-256 of the raw public key blob, base64-encoded
# without padding, prefixed "SHA256:" (matches OpenSSH).
require 'openssl'
require 'securerandom'
require 'digest/sha2'
module SSHKeygen
SSHKeyPair = Struct.new(:public_key, :private_key, :fingerprint,
keyword_init: true)
MAGIC = 'openssh-key-v1'.dup.force_encoding('BINARY') + "\x00".b
class << self
# Generate an SSH key pair in OpenSSH format. options:
# algorithm: 'ed25519' | 'rsa-2048' | 'rsa-4096'
# comment: optional key comment, conventionally `user@host`
def generate_ssh_key_pair(options = {})
algorithm = options[:algorithm]
comment = options[:comment].to_s.strip
suffix = comment.empty? ? '' : " #{comment}"
case algorithm
when 'ed25519' then ed25519_pair(suffix, comment)
when 'rsa-2048', 'rsa-4096'
rsa_pair(algorithm == 'rsa-4096' ? 4096 : 2048, suffix)
else
raise ArgumentError, "Unsupported algorithm: #{algorithm}"
end
end
private
# ---- binary helpers ----------------------------------------------------
def concat(*parts)
parts.join.b
end
def u32(n)
[n].pack('N')
end
# SSH "string": 4-byte big-endian length + raw bytes.
def ssh_string(data)
data = data.b unless data.encoding == Encoding::BINARY
u32(data.bytesize) + data
end
# SSH "mpint": big-endian integer, minimal, with a leading zero when the
# high bit is set. +bytes+ is a big-endian binary String (BN#to_s(2)).
def mpint(bytes)
bytes = bytes.sub(/\A\x00+/o, '') || bytes # strip redundant leading zeros
bytes = "\x00".b + bytes unless bytes.empty? || (bytes.getbyte(0) & 0x80).zero?
ssh_string(bytes)
end
def to_base64(bytes)
[bytes].pack('m0')
end
def pem_wrap(base64, label, width)
lines = base64.scan(/.{1,#{width}}/)
"-----BEGIN #{label}-----\n#{lines.join("\n")}\n-----END #{label}-----\n"
end
# "SHA256:<base64 digest of the public blob, unpadded>" - the OpenSSH
# fingerprint format.
def ssh_fingerprint(pub_blob)
"SHA256:#{to_base64(Digest::SHA256.digest(pub_blob)).sub(/=+\z/, '')}"
end
# ---- Ed25519 -----------------------------------------------------------
def ed25519_pair(suffix, comment)
pkey = OpenSSL::PKey::Ed25519.generate
# The SPKI wrapper is <...header...> 03 21 00 <32-byte key> and the
# PKCS#8 wrapper is <...header...> 04 20 <32-byte seed>; the raw values
# are always the trailing 32 bytes of either DER.
pub = pkey.public_to_der.bytes.last(32).pack('C*')
raise "Unexpected Ed25519 public key length: #{pub.bytesize}." if pub.bytesize != 32
pkcs8 = pkey.private_to_der
raise 'Unexpected Ed25519 private key encoding.' if pkcs8.bytesize < 34
seed = pkcs8.bytes.last(32).pack('C*')
pub_blob = ssh_string('ssh-ed25519') + ssh_string(pub)
SSHKeyPair.new(
public_key: "ssh-ed25519 #{to_base64(pub_blob)}#{suffix}",
private_key: open_ssh_private_pem(pub_blob, seed + pub, comment),
fingerprint: ssh_fingerprint(pub_blob)
)
end
# ---- RSA ---------------------------------------------------------------
def rsa_pair(bits, suffix)
rsa = OpenSSL::PKey::RSA.generate(bits)
pub_blob = concat(
ssh_string('ssh-rsa'),
mpint(rsa.e.to_s(2)), # public exponent e (big-endian)
mpint(rsa.n.to_s(2)) # modulus n
)
pkcs8 = rsa.private_to_der
SSHKeyPair.new(
public_key: "ssh-rsa #{to_base64(pub_blob)}#{suffix}",
private_key: pem_wrap(to_base64(pkcs8), 'PRIVATE KEY', 64),
fingerprint: ssh_fingerprint(pub_blob)
)
end
# ---- openssh-key-v1 ----------------------------------------------------
# Build the unencrypted "openssh-key-v1" private key structure for
# Ed25519 (cipher "none", kdf "none"), wrapped in an OPENSSH PRIVATE KEY
# PEM.
def open_ssh_private_pem(pub_blob, private_key_bytes, comment)
check = SecureRandom.random_bytes(4)
inner = concat(check, check, pub_blob, ssh_string(private_key_bytes),
ssh_string(comment))
# Pad to a multiple of the block size (8 for cipher "none") with 1,2,3,...
pad_len = (8 - (inner.bytesize % 8)) % 8
inner = concat(inner, (1..pad_len).to_a.pack('C*'))
outer = concat(
MAGIC,
ssh_string('none'), # ciphername
ssh_string('none'), # kdfname
ssh_string(''.b), # kdfoptions
u32(1), # number of keys
ssh_string(pub_blob),
ssh_string(inner)
)
pem_wrap(to_base64(outer), 'OPENSSH PRIVATE KEY', 70)
end
end
end
Also available in 8 other languages
Every CosmoDev tool ships its pure logic in TypeScript (web) and Go (CLI), with authored implementations in a dozen-plus languages — the same contract, ported. Compare all languages side by side →