Aller au contenu
ecdsa.com

Library Interop Cheatsheet — interopérabilité des bibliothèques

Vous signez avec la bibliothèque A et vérifiez avec la bibliothèque B : qu’est-ce qui casse ? Choisissez les deux côtés et obtenez la liste de contrôle des incompatibilités concrètes (encodage de signature, gestion de high-S, format de clé publique et nonces déterministes), chacune avec la ligne de code qui la corrige.

Données de référence statiques : aucune saisie, aucun appel réseau, rien à envoyer nulle part

openssl dgst / openssl pkeyutl

navigateurs, Deno, webcrypto de Node

Signataire
Vérificateur
Bibliothèque
OpenSSL CLI
WebCrypto (crypto.subtle)
Signature
ASN.1 DER
r‖s brut (P1363)
Low-S
Accepte les deux · signe low-S ou high-S
Accepte les deux · signe low-S ou high-S
RFC 6979
À activer
Non (nonce aléatoire)
Clé publique
SPKI PEM
JWK
Courbes
P-256, P-384, P-521, secp256k1
P-256, P-384, P-521

Signature avec OpenSSL CLI → vérification avec WebCrypto (crypto.subtle)

2 points à corriger pour que cela fonctionne
Bloquant

Incompatibilité de format de signature : ASN.1 DER en sortie, r‖s brut attendu

OpenSSL CLI produit par défaut le format ASN.1 DER et WebCrypto (crypto.subtle) attend le format r‖s brut. Ni la clé ni les calculs ne sont en cause : le vérificateur ne peut tout simplement pas lire les octets, car un blob DER de longueur variable n’a pas la bonne longueur pour un analyseur à largeur fixe.

Corrigez le problème d’un côté ou de l’autre : les deux encodages portent exactement la même information, la conversion est donc sans perte et ne fait intervenir aucune cryptographie.

Côté signataireProduire le format r‖s brut avec OpenSSL CLI

Aucune option intégrée

La CLI ne propose aucune sortie P1363 pour ECDSA. Extrayez les deux INTEGER de la structure DER et complétez chacun à gauche par des zéros jusqu’à la taille de la courbe (32 octets pour P-256) ; `openssl asn1parse -inform DER -in sig.der` affiche r et s, ce qui permet de vérifier la conversion à l’œil.

Côté vérificateurAccepter le format ASN.1 DER dans WebCrypto (crypto.subtle)

Aucune option intégrée

Il n’existe pas de mode DER. Coupez la sortie de longueur fixe en deux, retirez les zéros de tête de chaque moitié et enveloppez les deux entiers dans une SEQUENCE DER avant de les transmettre à un vérificateur de type OpenSSL.

† Le comportement varie selon les versions ou les builds : vérifiez dans votre version.

Voir aussi : Convertisseur DER ⇄ brut · Formats de signature ECDSA : DER ou brut

À vérifier

Formats de clé publique incompatibles : SPKI PEM face à JWK

OpenSSL CLI exporte la clé publique sous la forme SPKI PEM, et WebCrypto (crypto.subtle) attend la forme JWK. Ce sont des conteneurs réellement différents : SPKI enveloppe le point avec l’identifiant de la courbe, une JWK détaille les coordonnées x et y dans des champs base64url, et un point SEC1 n’est rien d’autre que les octets des coordonnées. Le matériel de clé sous-jacent est identique dans les trois cas.

OpenSSL CLI : `openssl ec -pubin -in pub.pem -pubout -outform DER` donne la même clé en SPKI DER brut ; `openssl ec -pubin -in pub.pem -text -noout` affiche en hexadécimal le point non compressé au format SEC1.

WebCrypto (crypto.subtle) : importKey("spki", derBytes, …) attend du SPKI *DER* : retirez d’abord l’en-tête et la ligne de fin PEM, puis décodez le base64, car le texte PEM lui-même est refusé.

Voir aussi : Vérificateur de signatures ECDSA

Bon à savoir

Restez sur P-256 / P-384 / P-521 : les courbes prises en charge des deux côtés

Les ensembles de courbes diffèrent : secp256k1 uniquement dans OpenSSL CLI. Choisissez une clé sur une courbe commune et la question ne se pose jamais.

OpenSSL CLI : Tout ce que le build active : les courbes NIST sur corps premiers, secp256k1, Brainpool et d’autres encore ; `openssl ecparam -list_curves` fait foi pour votre binaire.

WebCrypto (crypto.subtle) : Uniquement les trois courbes NIST. secp256k1 ne fait pas partie de la spécification : les clés Bitcoin et Ethereum nécessitent donc plutôt une bibliothèque JavaScript.

Bon à savoir

OpenSSL CLI utilise un nonce aléatoire, sauf si vous activez la RFC 6979

Le chemin de signature habituel utilise un nonce aléatoire. Depuis la version 3.2, le paramètre de signature `nonce-type:1` sélectionne l’ECDSA déterministe (RFC 6979), avec un algorithme de condensat explicite : `openssl dgst -sha256 -sign key.pem -sigopt nonce-type:1` ou `pkeyutl -pkeyopt nonce-type:1 -pkeyopt digest:sha256`.

C’est un comportement correct, pas un bug. Mais cela signifie que la même clé et le même message produisent des octets différents à chaque exécution : un test qui compare les signatures octet par octet échouera. Comparez plutôt les résultats de vérification.

† Le comportement varie selon les versions ou les builds : vérifiez dans votre version.

Toutes les bibliothèques, côte à côte

Valeurs par défaut uniquement : chaque ligne peut être modifiée par une option ou un autre appel. Faites défiler horizontalement pour voir toutes les colonnes.

BibliothèqueFormat de signature par défautComment changerPolitique low-SRFC 6979Clé publique par défaut
OpenSSL CLI
openssl dgst / openssl pkeyutl
signataire
ASN.1 DER
ASN.1 DER : une SEQUENCE de deux INTEGER, si bien qu’une signature P-256 fait généralement de 70 à 72 octets et que sa longueur varie d’une signature à l’autre.
Pas de mode r‖s brut dans la CLI : convertissez la sortie DER avec un outil externe.†
Accepte les deux
Aucune politique low-S, dans un sens comme dans l’autre : environ une signature sur deux sort avec s dans la moitié supérieure, et la vérification ne s’en soucie jamais.
À activer
Le chemin de signature habituel utilise un nonce aléatoire. Depuis la version 3.2, le paramètre de signature `nonce-type:1` sélectionne l’ECDSA déterministe (RFC 6979), avec un algorithme de condensat explicite : `openssl dgst -sha256 -sign key.pem -sigopt nonce-type:1` ou `pkeyutl -pkeyopt nonce-type:1 -pkeyopt digest:sha256`.†
SPKI PEM
SPKI PEM : le bloc `-----BEGIN PUBLIC KEY-----` écrit par `openssl ec -pubout`. Notez que `-----BEGIN EC PUBLIC KEY-----` n’existe pas : un bloc étiqueté EC est une clé privée.
Node.js node:crypto
crypto.sign / crypto.verify / KeyObject
ASN.1 DER
ASN.1 DER, sauf si vous demandez explicitement l’autre encodage.
`dsaEncoding: "ieee-p1363"` sur l’objet clé : définissez-le à la signature *et* à la vérification.
Accepte les deux
Accepte les deux moitiés et ne normalise rien : sous le capot, c’est OpenSSL.
Non (nonce aléatoire)
Nonce aléatoire : la même clé et le même message produisent des octets de signature différents à chaque appel.
SPKI PEM
`publicKey.export({ type: "spki", format: "pem" })` est la forme courante ; un seul appel à `export({ format: "jwk" })` suffit pour obtenir la JWK.
WebCrypto (crypto.subtle)
navigateurs, Deno, webcrypto de Node
vérificateur
r‖s brut (P1363)
Uniquement r‖s brut, de longueur fixe : 64 octets pour P-256, 96 pour P-384, 132 pour P-521. La spécification ne définit aucun autre encodage.
Rien à changer : WebCrypto ne parle jamais DER. Convertissez en dehors de l’API.
Accepte les deux
Aucune règle low-S, ni à la signature ni à la vérification.
Non (nonce aléatoire)
Nonce aléatoire, et l’API ne vous permet pas de fournir le vôtre.
JWK
Vous indiquez toujours un format à l’importation et à l’exportation : « jwk », « spki » (octets DER) ou « raw » (le point non compressé au format SEC1). La JWK est la forme que fait circuler la plupart du code web.
Go crypto/ecdsa
SignASN1 / VerifyASN1, ou la paire de big.Int
ASN.1 DER
`SignASN1` (le chemin qu’emprunte `crypto.Signer`) renvoie de l’ASN.1 DER. `Sign`, de plus bas niveau, renvoie r et s sous forme de `*big.Int`, ce qui ne correspond à aucun des deux formats de transmission avant encodage.
`SignASN1`/`VerifyASN1` pour le DER ; `Sign`/`Verify` avec `FillBytes` pour le format brut à largeur fixe.
Accepte les deux
Aucune gestion de low-S : la vérification accepte l’une ou l’autre moitié de l’intervalle.
Non (nonce aléatoire)
Non compatible avec la RFC 6979. Les versions récentes dérivent le nonce à la fois de la clé, du message et d’un aléa frais : les octets de signature changent donc à chaque exécution et ne peuvent pas être figés dans des tests.†
SPKI DER
`x509.MarshalPKIXPublicKey` produit du SPKI DER ; un simple appel à `pem.EncodeToMemory` suffit pour en faire un bloc PEM.
Python cryptography (pyca)
hazmat.primitives.asymmetric.ec
ASN.1 DER
DER : `private_key.sign(...)` renvoie la SEQUENCE ASN.1.
`decode_dss_signature` / `encode_dss_signature` font passer du DER à la paire d’entiers et inversement ; le remplissage à largeur fixe est à votre charge.
Accepte les deux
Accepte les deux moitiés ; rien ne normalise s.
À activer
Nonce aléatoire par défaut. Les versions récentes acceptent une option de signature déterministe sur l’objet ECDSA, et cette voie exige un OpenSSL sous-jacent récent : vérifiez avec quelle version d’OpenSSL votre fichier wheel a été compilé avant de vous y fier.†
SPKI PEM
`public_bytes(Encoding.PEM, PublicFormat.SubjectPublicKeyInfo)` : le même bloc PEM que celui qu’écrit OpenSSL.
Python ecdsa (Python pur)
SigningKey / VerifyingKey, sigencode= et sigdecode=
r‖s brut (P1363)
r‖s brut : `sign()` renvoie les deux valeurs concaténées et complétées par des zéros jusqu’à la taille de la courbe.
Les arguments `sigencode=` et `sigdecode=` choisissent l’encodage à chaque appel de signature et de vérification.
Accepte les deux
Les deux moitiés sont acceptées. La bibliothèque fournit aussi des encodeurs de canonicalisation qui forcent s dans la moitié inférieure : ils coexistent avec les encodeurs simples et doivent être sélectionnés explicitement.†
À activer
La signature déterministe RFC 6979 est disponible sous forme de méthode de signature distincte ; l’appel sign() simple utilise de l’aléa.
point SEC1
Les clés circulent sous forme de point nu : `VerifyingKey.to_string()` renvoie par défaut x‖y sans préfixe 0x04, ce que les environnements fondés sur SPKI n’acceptent pas tel quel.†
Java JCA (SunEC)
java.security.Signature
ASN.1 DER
`SHA256withECDSA` produit de l’ASN.1 DER.
Ajoutez `inP1363Format` au nom de l’algorithme : même classe, autre format de transmission.
Accepte les deux
Aucune politique low-S, dans un sens comme dans l’autre.
Non (nonce aléatoire)
SunEC signe avec un nonce aléatoire. L’ECDSA déterministe est accessible en ajoutant un fournisseur tiers comme Bouncy Castle.†
SPKI DER
`publicKey.getEncoded()` renvoie du SPKI DER X.509. Le JDK ne prend pas en charge PEM : l’enveloppe base64 est à votre charge.
Rust p256 / k256 (RustCrypto)
ecdsa::Signature, SigningKey, VerifyingKey
r‖s brut (P1363)
Le type `Signature` par défaut est la paire à largeur fixe : 64 octets pour P-256 et secp256k1. Le DER passe par une conversion distincte et explicite.†
La paire à largeur fixe est le type par défaut ; la conversion DER est explicite dans les deux sens.†
Accepte les deux
La crate secp256k1 normalise s dans la moitié inférieure à la signature, conformément à la convention de Bitcoin ; les crates des courbes NIST ne le font pas. La vérification accepte les deux : normalisez donc côté signature quand l’autre extrémité est stricte.†
Oui, par défaut
Nonces déterministes RFC 6979 par défaut : la même clé et le même message produisent toujours les mêmes octets. Une variante aléatoire doit être activée explicitement.
point SEC1
Une `VerifyingKey` se sérialise en point encodé SEC1 : 33 octets sous forme compressée, 65 sous forme non compressée. SPKI DER et PEM sont disponibles via les traits SPKI/PKCS#8 associés.†
libsecp256k1 (Bitcoin Core)
secp256k1_ecdsa_sign / _verify
Au choix
Ni l’un ni l’autre tant que vous ne sérialisez pas : une signature vit dans une structure interne opaque, et vous choisissez compact (r‖s brut) ou DER à la sortie.
Sérialisez en compact pour r‖s brut, en DER pour le format de transmission de Bitcoin : le choix se fait à chaque appel.
Impose low-S
La stricte du lot. La signature produit toujours du low-S, et la vérification rejette d’emblée toute signature high-S : la bibliothèque expose un appel de normalisation précisément pour vous permettre de corriger les signatures venues d’un signataire généraliste.
Oui, par défaut
RFC 6979 par défaut ; une autre fonction de nonce peut être fournie si besoin.
point SEC1
Points SEC1 : 33 octets sous forme compressée ou 65 octets sous forme non compressée. Aucune trace de SPKI, de PEM ni de JWK dans l’API.
PHP openssl_*
openssl_sign / openssl_verify
ASN.1 DER
DER : `openssl_sign()` renvoie exactement ce qu’OpenSSL a produit.
Aucun mode brut : convertissez dans votre propre code PHP ou utilisez une bibliothèque JOSE qui le fait déjà.
Accepte les deux
Aucune politique low-S : le comportement d’OpenSSL est hérité tel quel.
Non (nonce aléatoire)
Nonce aléatoire ; l’extension n’expose aucun mode déterministe.
SPKI PEM
Des chaînes PEM en entrée comme en sortie : `openssl_pkey_get_public()` accepte un bloc `-----BEGIN PUBLIC KEY-----` ou un certificat complet.
Ruby OpenSSL
OpenSSL::PKey::EC
ASN.1 DER
DER : `key.sign(digest, data)` renvoie la SEQUENCE ASN.1.
Aucun mode brut intégré : convertissez à la main via OpenSSL::ASN1 ou confiez-le à une gem JOSE.
Accepte les deux
Aucune politique low-S : le comportement d’OpenSSL, inchangé.
Non (nonce aléatoire)
Nonce aléatoire ; aucune option déterministe n’est exposée.
SPKI PEM
SPKI PEM. Les versions récentes fournissent une méthode dédiée à l’exportation de la clé publique, qu’il vaut la peine d’utiliser, car un simple `to_pem` sur une clé qui contient une partie privée écrit la clé privée.†

† Le comportement varie selon les versions ou les builds : vérifiez dans votre version.

Pour aller plus loin

How it works

A signature that verifies in one library and fails in another is almost never wrong mathematically: the two sides simply disagree about packaging. An ECDSA signature is a pair of integers (r, s), and each ecosystem picked its own way to serialize them — ASN.1 DER in the OpenSSL and X.509 lineage, fixed-width r‖s in WebCrypto, JWS and most blockchain stacks. On top of that sit two more sources of disagreement: whether s must be in the lower half of the range (the low-S rule that Bitcoin enforces and general-purpose libraries ignore), and how the public key itself is wrapped — an SPKI structure, a JWK object, or the bare SEC1 point.

This page compares those defaults for a pair of libraries and lists only the differences that actually cause a failure, together with the switch or conversion that removes each one. Every row describes what a library does when you call the obvious API without extra arguments — not what it is capable of. Nearly all of it is configurable, which is exactly why two teams reading the same documentation end up incompatible, and why the fix is usually one option rather than new code.