Aller au contenu
ecdsa.com
Ruby

Ruby OpenSSL (openssl gem)

La gem openssl de Ruby, la liaison sur laquelle reposent toutes les bibliothèques cryptographiques et JWT de Ruby : signatures DER, exceptions au lieu de false pour une entrée mal formée, clés immuables sous OpenSSL 3 et ML-DSA via l’API PKey générique.
Licence
Ruby OR BSD-2-Clause
Version vérifiée
4.0.2 ·
Code source
github.com/ruby/openssl
Documentation
docs.ruby-lang.org/en/master/OpenSSL.html

Compatibilité en un coup d’œil

ECDSAEdDSAPost-quantique

Courbes ECDSA
P-224P-256P-384P-521secp256k1brainpoolP256r1brainpoolP384r1brainpoolP512r1
EdDSA
Ed25519Ed448
Formats de clé publique
spki-pemspki-der
Formats de clé privée
pkcs8-pempkcs8-derencrypted-pkcs8sec1-pem

La gem openssl est livrée avec Ruby et encapsule l’OpenSSL du système (3.5.7 dans l’image ruby:4.0.7, avec la gem en version 4.0.2). OpenSSL::PKey::EC#sign(digest, data) produit de l’ASN.1 DER avec un nonce aléatoire et sans normalisation low-S, et verify accepte les valeurs high-S. dsa_sign_asn1 signe un condensat que vous avez déjà calculé.

Les signatures mal formées lèvent une exception au lieu de renvoyer false : une signature r‖s brute de 64 octets ou un blob de 3 octets a levé OpenSSL::PKey::PKeyError: EVP_DigestVerify, tandis qu’une signature bien formée portant sur un autre message a renvoyé false. Un code qui ne teste que le booléen plante sur une signature de type JWS. Interceptez donc PKeyError ou convertissez d’abord la signature, comme le fait l’exemple ; le convertisseur DER ⇄ brut montre les deux formes côte à côte.

Sous OpenSSL 3, les clés sont immuables : affecter private_key sur une clé EC existante a levé pkeys are immutable on OpenSSL 3.0, ce qui casse les anciens tutoriels qui construisent une clé en définissant le point ou le scalaire. Construisez plutôt les clés à partir de DER ou de PEM ; un point SEC1 nu doit d’abord être enveloppé dans une structure SPKI. Notez bien ce qu’écrit chaque méthode : to_pem sur une clé privée écrit une EC PRIVATE KEY SEC1, secret compris, private_to_pem écrit du PKCS#8, et c’est public_to_pem qu’il faut publier.

Les courbes prises en charge sont celles d’OpenSSL : de P-224 à P-521, secp256k1 et les courbes Brainpool r1, ainsi qu’Ed25519 et Ed448 via OpenSSL::PKey.generate_key. La même API générique a généré des clés ML-DSA-44/65/87 et SLH-DSA, puis a signé et vérifié avec elles ; les clés ML-KEM peuvent être générées, mais PKey n’offre aucune méthode d’encapsulation dans la version 4.0.2. Pour les JWT, ruby-jwt s’appuie sur cette gem.

Les faits et les vérifications qui les fondent

Rien de ce qui suit n’est copié d’une documentation. La vérification content/libraries/verify/ruby-openssl/run.sh a exécuté Ruby OpenSSL (openssl gem) 4.0.2 sur Ruby 4.0.7, OpenSSL 3.5.7 9 Jun 2026 dans l’image de conteneur ruby:4.0.7-slim-trixie le 20 octobre 2026 ; la page doit correspondre à ce que la vérification a affiché, et chaque fait cite les lignes en question.

  • Encodage des signatures ECDSA

    der

    sign(digest, data) renvoie de l’ASN.1 DER.

    Relevé par la vérification
    fact ecdsa-signature-encoding = der
  • Nonce ECDSA

    random

    Nonce aléatoire : deux signatures du même message diffèrent.

    Relevé par la vérification
    fact ecdsa-nonce = random
  • Low-S à la signature

    either

    s n’est pas normalisé : des valeurs high-S sont apparues parmi 64 signatures.

    Relevé par la vérification
    fact ecdsa-low-s-sign = either
  • High-S à la vérification

    accepts-high-s

    verify a accepté la signature dont s avait été remplacé par n − s.

    Relevé par la vérification
    fact ecdsa-low-s-verify = accepts-high-s
  • Ce que l’appel de signature hache

    hashes-message-or-digest

    sign hache avec l’algorithme de condensat indiqué ; dsa_sign_asn1 signe un condensat précalculé, et cette signature a été vérifiée sur le message.

    Relevé par la vérification
    fact ecdsa-prehash = hashes-message-or-digest
  • Signature ECDSA mal formée

    throws

    Une signature r‖s brute et un blob de 3 octets lèvent OpenSSL::PKey::PKeyError: EVP_DigestVerify ; une signature valide portant sur un autre message renvoie false.

    Relevé par la vérification
    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
  • Courbes ECDSA

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

    Signature et vérification réussies avec P-224, P-256, P-384, P-521, secp256k1 et brainpoolP256r1/P384r1/P512r1.

    Relevé par la vérification
    fact ecdsa-curves = P-224, P-256, P-384, P-521, secp256k1, brainpoolP256r1, brainpoolP384r1, brainpoolP512r1
  • EdDSA

    Ed25519Ed448

    Ed25519 et Ed448 via OpenSSL::PKey.generate_key.

    Relevé par la vérification
    fact eddsa = Ed25519, Ed448
  • Algorithmes post-quantiques

    ML-DSASLH-DSA

    Génération, signature et vérification réussies avec ML-DSA-44/65/87 et SLH-DSA-SHA2-128s via l’API PKey générique ; les clés ML-KEM se génèrent, mais il n’existe aucune méthode d’encapsulation.

    Relevé par la vérification
    fact pqc = ML-DSA, SLH-DSA
    evidence pqc: ML-KEM-768 key generation, and whether PKey offers encapsulate: false
  • Formats de clé publique

    spki-pemspki-der

    SPKI PEM et DER via OpenSSL::PKey.read ; un point SEC1 nu ne s’est chargé qu’une fois enveloppé dans du SPKI.

    Relevé par la vérification
    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
  • Formats de clé privée

    pkcs8-pempkcs8-derencrypted-pkcs8sec1-pem

    PKCS#8 PEM et DER, PKCS#8 chiffré et SEC1 PEM ; to_pem écrit EC PRIVATE KEY, private_to_pem écrit du PKCS#8.

    Relevé par la vérification
    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-----
  • Objets de clé EC modifiables

    no

    Affecter private_key sur une clé existante a levé pkeys are immutable on OpenSSL 3.0.

    Relevé par la vérification
    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

Utilisation

Signer en DER, convertir en r‖s brut, ne publier que la clé publique et constater que verify lève une exception sur une signature brute.
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

Le banc de test des extraits de code exécute chaque exemple tel quel dans l’image de conteneur ruby:4.0.7-slim-trixie, et l’exécution échoue si la sortie ne correspond pas.

Commande
ruby usage.rb
La sortie doit correspondre à cette expression régulière
verified: true, raw bytes: 64\nraw signature: OpenSSL::PKey::PKeyError

Bibliothèques