Skip to content
ecdsa.com

Generate a key pair · Ruby

How to generate an ECDSA key pair in Ruby

To generate an ECDSA key pair in Ruby, call OpenSSL::PKey::EC.generate with a curve name and write the halves with private_to_pem and public_to_pem — the PKCS#8 and SPKI files every other stack reads. Both methods matter: the older to_pem still writes the legacy SEC 1 format.

Tested with openssl (Ruby gem) 4.0.2 (Ruby 4.0.7, OpenSSL 3.5.7) and OpenSSL 3.5.7 (openssl pkey cross-check).

generate.rb — a P-256 key pair written as PEM files
require "openssl"
key = OpenSSL::PKey::EC.generate("prime256v1") # P-256 under its OpenSSL name; "P-256" is rejected
# EXCL refuses to overwrite an existing key; 0o600 keeps the file owner-only
File.open("key.pem", File::WRONLY | File::CREAT | File::EXCL, 0o600) do |file|
file.write(key.private_to_pem) # "BEGIN PRIVATE KEY" (PKCS#8)
end
File.write("pub.pem", key.public_to_pem) # "BEGIN PUBLIC KEY" (SPKI)
puts key.public_to_pem
a passphrase-protected private key
# "BEGIN ENCRYPTED PRIVATE KEY": PKCS#8 under PBES2 with AES-256-CBC
encrypted = key.private_to_pem(OpenSSL::Cipher.new("aes-256-cbc"), ENV.fetch("KEY_PASSPHRASE"))
File.write("key-encrypted.pem", encrypted, perm: 0o600)

How it works

  1. OpenSSL::PKey::EC.generate("prime256v1") creates the key in one step. The older EC.new(curve).generate_key! from many tutorials fails when Ruby is linked against OpenSSL 3: pkeys are immutable on OpenSSL 3.0.
  2. private_to_pem writes PKCS#8 (BEGIN PRIVATE KEY) and public_to_pem writes SPKI (BEGIN PUBLIC KEY); the run checks with openssl pkey that the two belong together.
  3. File::EXCL with mode 0o600 creates the key file owner-only and fails if it already exists, so a second run cannot overwrite a key in use.
  4. private_to_pem(cipher, passphrase) encrypts the PKCS#8 structure (BEGIN ENCRYPTED PRIVATE KEY); OpenSSL::PKey.read(pem, passphrase) and openssl pkey -passin both open it.

Gotchas

  • Curve names are OpenSSL's: "prime256v1", "secp384r1", "secp521r1", "secp256k1". Both "P-256" and "secp256r1" raise OpenSSL::PKey::PKeyError: invalid curve name.
  • key.to_pem (and its alias export) writes BEGIN EC PRIVATE KEY — the SEC 1 format that WebCrypto, Java and .NET's PKCS#8 importers reject. Use private_to_pem for anything that leaves Ruby.
  • The perm: option of File.write applies only when the file is created; an existing world-readable key file keeps its mode. The File::EXCL form above refuses to reuse a file at all.
  • OpenSSL::PKey::EC#to_text and #private_key expose the private scalar; never log or inspect key objects in production.

Related recipes