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.