Saltar al contenido
ecdsa.com
Ruby

Ruby OpenSSL (openssl gem)

La gema openssl de Ruby, el binding sobre el que se apoyan todas las bibliotecas criptográficas y de JWT de Ruby: firmas DER, excepciones en lugar de false ante una entrada mal formada, claves inmutables en OpenSSL 3 y ML-DSA a través de la API genérica PKey.
Licencia
Ruby OR BSD-2-Clause
Versión comprobada
4.0.2 ·
Código fuente
github.com/ruby/openssl
Documentación
docs.ruby-lang.org/en/master/OpenSSL.html

Compatibilidad de un vistazo

ECDSAEdDSACriptografía poscuántica

Curvas ECDSA
P-224P-256P-384P-521secp256k1brainpoolP256r1brainpoolP384r1brainpoolP512r1
EdDSA
Ed25519Ed448
Formatos de clave privada
pkcs8-pempkcs8-derencrypted-pkcs8sec1-pem

La gema openssl viene con Ruby y envuelve el OpenSSL del sistema: la 3.5.7 en la imagen ruby:4.0.7, con la versión 4.0.2 de la gema. OpenSSL::PKey::EC#sign(digest, data) produce DER ASN.1 con un nonce aleatorio y sin normalización low-S, y verify acepta high-S. dsa_sign_asn1 firma un resumen ya calculado.

Las firmas mal formadas lanzan una excepción en lugar de devolver false: una firma r‖s en bruto de 64 bytes o un blob de 3 bytes lanzaron OpenSSL::PKey::PKeyError: EVP_DigestVerify, mientras que una firma bien formada sobre otro mensaje devolvió false. El código que solo comprueba el booleano termina con una excepción ante una firma al estilo JWS, así que captura PKeyError o convierte antes la firma, como hace el ejemplo; el conversor DER ⇄ en bruto muestra las dos formas una al lado de la otra.

Las claves son inmutables en OpenSSL 3: asignar private_key en una clave EC existente lanzó pkeys are immutable on OpenSSL 3.0, lo que rompe los tutoriales antiguos que construyen una clave asignando el punto o el escalar. En su lugar, construye las claves a partir de DER o PEM: un punto SEC1 suelto tiene que envolverse primero en una estructura SPKI. Fíjate en qué escribe cada método: to_pem en una clave privada escribe SEC1 EC PRIVATE KEY, secreto incluido; private_to_pem escribe PKCS#8, y public_to_pem es el que hay que publicar.

Las curvas disponibles son las de OpenSSL: de P-224 a P-521, secp256k1 y las curvas Brainpool r1, además de Ed25519 y Ed448 mediante OpenSSL::PKey.generate_key. La misma API genérica generó claves ML-DSA-44/65/87 y SLH-DSA, y firmó y verificó con ellas; las claves ML-KEM se pueden generar, pero PKey no ofrece ningún método de encapsulación en la 4.0.2. Para los JWT, ruby-jwt se apoya en esta gema.

Datos y las comprobaciones que los respaldan

Nada de lo que sigue está copiado de la documentación. La comprobación content/libraries/verify/ruby-openssl/run.sh ejecutó Ruby OpenSSL (openssl gem) 4.0.2 en Ruby 4.0.7, OpenSSL 3.5.7 9 Jun 2026 en la imagen de contenedor ruby:4.0.7-slim-trixie el 20 de octubre de 2026; la página debe coincidir con lo que imprimió, y cada dato cita esas líneas.

  • Codificación de la firma ECDSA

    der

    sign(digest, data) devuelve DER ASN.1.

    Registrado por la comprobación
    fact ecdsa-signature-encoding = der
  • Nonce de ECDSA

    random

    Nonce aleatorio: dos firmas del mismo mensaje son distintas.

    Registrado por la comprobación
    fact ecdsa-nonce = random
  • Low-S al firmar

    either

    s no se normaliza: aparecieron valores high-S entre 64 firmas.

    Registrado por la comprobación
    fact ecdsa-low-s-sign = either
  • High-S al verificar

    accepts-high-s

    verify aceptó la firma con s sustituido por n − s.

    Registrado por la comprobación
    fact ecdsa-low-s-verify = accepts-high-s
  • De qué calcula el hash la llamada de firma

    hashes-message-or-digest

    sign calcula el hash con el algoritmo de resumen indicado; dsa_sign_asn1 firma un resumen ya calculado, y esa firma se verificó contra el mensaje.

    Registrado por la comprobación
    fact ecdsa-prehash = hashes-message-or-digest
  • Firma ECDSA mal formada

    throws

    Una firma r‖s en bruto y un blob de 3 bytes lanzan OpenSSL::PKey::PKeyError: EVP_DigestVerify; una firma válida sobre otro mensaje devuelve false.

    Registrado por la comprobación
    fact ecdsa-malformed-signature = throws
    evidence ecdsa-malformed-signature: raw r||s: OpenSSL::PKey::PKeyError: EVP_DigestVerify
    evidence ecdsa-malformed-signature: 3 bytes: OpenSSL::PKey::PKeyError: EVP_DigestVerify
    evidence ecdsa-malformed-signature: wrong message: false
  • Curvas ECDSA

    P-224P-256P-384P-521secp256k1brainpoolP256r1brainpoolP384r1brainpoolP512r1

    Con P-224, P-256, P-384, P-521, secp256k1 y brainpoolP256r1/P384r1/P512r1 se firmó y se verificó.

    Registrado por la comprobación
    fact ecdsa-curves = P-224, P-256, P-384, P-521, secp256k1, brainpoolP256r1, brainpoolP384r1, brainpoolP512r1
  • EdDSA

    Ed25519Ed448

    Ed25519 y Ed448 mediante OpenSSL::PKey.generate_key.

    Registrado por la comprobación
    fact eddsa = Ed25519, Ed448
  • Algoritmos poscuánticos

    ML-DSASLH-DSA

    Mediante la API genérica PKey se generaron claves ML-DSA-44/65/87 y SLH-DSA-SHA2-128s, y con ellas se firmó y se verificó; las claves ML-KEM se generan, pero no hay ningún método de encapsulación.

    Registrado por la comprobación
    fact pqc = ML-DSA, SLH-DSA
    evidence pqc: ML-KEM-768 key generation, and whether PKey offers encapsulate: false
  • Formatos de clave pública

    spki-pemspki-der

    SPKI PEM y DER mediante OpenSSL::PKey.read; un punto SEC1 suelto solo se cargó después de envolverlo en SPKI.

    Registrado por la comprobación
    fact public-key-formats = spki-pem, spki-der
    evidence public-key-formats: a 65-byte SEC1 point wrapped in an SPKI structure by hand, then loaded: ok
  • Formatos de clave privada

    pkcs8-pempkcs8-derencrypted-pkcs8sec1-pem

    PKCS#8 PEM y DER, PKCS#8 cifrado y SEC1 PEM; to_pem escribe EC PRIVATE KEY y private_to_pem escribe PKCS#8.

    Registrado por la comprobación
    fact private-key-formats = pkcs8-pem, pkcs8-der, encrypted-pkcs8, sec1-pem
    evidence private-key-formats: key.to_pem writes -----BEGIN EC PRIVATE KEY-----; key.private_to_pem writes -----BEGIN PRIVATE KEY-----
  • Objetos de clave EC mutables

    no

    Asignar private_key en una clave existente lanzó pkeys are immutable on OpenSSL 3.0.

    Registrado por la comprobación
    fact ec-key-mutable = no
    evidence ec-key-mutable: assigning private_key on an existing EC key: OpenSSL::PKey::PKeyError: pkeys are immutable on OpenSSL 3.0

Uso

Firma en DER, convierte a r‖s en bruto, publica solo la clave pública y comprueba que verify lanza una excepción con una firma en bruto.
require "openssl"

key = OpenSSL::PKey::EC.generate("prime256v1")
digest = OpenSSL::Digest.new("SHA256")
message = "order #1042 approved"

der = key.sign(digest, message) # ASN.1 DER

# Raw r||s for JWS or WebCrypto: take the two INTEGERs and pad each to 32 bytes.
r, s = OpenSSL::ASN1.decode(der).value.map(&:value)
raw = r.to_s(2).rjust(32, "\0") + s.to_s(2).rjust(32, "\0")

# Publish only the public half: public_to_pem, never to_pem (which writes the private key).
public_key = OpenSSL::PKey.read(key.public_to_pem)
puts "verified: #{public_key.verify(digest, der, message)}, raw bytes: #{raw.bytesize}"

# A raw signature is not "false" here: verify raises OpenSSL::PKey::PKeyError.
begin
  public_key.verify(digest, raw, message)
rescue OpenSSL::PKey::PKeyError => e
  puts "raw signature: #{e.class}"
end

El arnés de pruebas de fragmentos de código ejecuta cada ejemplo tal cual en la imagen de contenedor ruby:4.0.7-slim-trixie, y la ejecución falla si la salida no coincide.

Comando
ruby usage.rb
La salida debe coincidir (expresión regular)
verified: true, raw bytes: 64\nraw signature: OpenSSL::PKey::PKeyError

Bibliotecas