Aller au contenu
ecdsa.com
Node.js

Node.js node:crypto

Le module crypto intégré à Node.js : signatures ECDSA, EdDSA, RSA et post-quantiques reposant sur l’OpenSSL livré avec chaque version de Node.js, avec par défaut des signatures DER, des nonces aléatoires et aucune politique low-S.
Licence
MIT
Version vérifiée
24.22.0 ·
Code source
github.com/nodejs/node
Documentation
nodejs.org/api/crypto.html

Compatibilité en un coup d’œil

ECDSAEdDSAPost-quantique

Courbes ECDSA
P-224P-256P-384P-521secp256k1brainpoolP256r1brainpoolP384r1brainpoolP512r1
EdDSA
Ed25519Ed448
Algorithmes post-quantiques
ML-KEMML-DSASLH-DSA
Formats de clé publique
spki-pemspki-derx509jwksec1-uncompressedsec1-compressed
Formats de clé privée
pkcs8-pempkcs8-derencrypted-pkcs8sec1-pemsec1-derjwkraw-scalar

node:crypto est le module cryptographique livré avec chaque version de Node.js. Ses fonctions en un seul appel crypto.sign() et crypto.verify() et la classe KeyObject couvrent ECDSA, Ed25519, Ed448, RSA et, depuis Node.js 24.22, ML-DSA, SLH-DSA et ML-KEM. Tout repose sur l’OpenSSL que Node.js embarque (3.5.8 dans Node.js 24.22.0) : la plupart des comportements reproduisent donc ceux de la ligne de commande OpenSSL, et diffèrent de l’API WebCrypto que le même processus expose sous crypto.subtle.

Le bug d’interopérabilité que vous rencontrerez le plus probablement concerne l’encodage de la signature. crypto.sign() renvoie de l’ASN.1 DER, alors que WebCrypto, JWS/JWT, .NET et la plupart des portefeuilles matériels utilisent la forme r‖s brute de largeur fixe. Une signature dans l’autre encodage ne lève pas d’exception : crypto.verify() renvoie simplement false, ce qui ressemble en tout point à une signature falsifiée. Définissez dsaEncoding: "ieee-p1363" dans l’objet clé, côté signature comme côté vérification, ou convertissez les octets avec le convertisseur DER ⇄ brut.

Deux comportements par défaut plus discrets comptent quand l’autre côté est strict. Les signatures ne sont jamais normalisées en low-S, si bien qu’environ la moitié d’entre elles sont rejetées par les vérificateurs qui l’exigent, comme libsecp256k1 et @noble/curves 2.x. De plus, le nonce est aléatoire : la même clé et le même message donnent des octets différents à chaque appel. Dans les tests, comparez donc les résultats de vérification, jamais les octets des signatures. Si un signataire répète un jour un nonce, le vérificateur de réutilisation de nonce retrouve la clé ; c’est pourquoi un nonce aléatoire exige une source d’aléa saine.

Si vous passez null comme algorithme, la signature avec une clé EC utilise discrètement SHA-256, quelle que soit sa courbe ; une clé P-384 échoue alors face à tout pair qui attend SHA-384. Indiquez la fonction de hachage à chaque appel, chargez les clés une seule fois avec crypto.createPublicKey() ou crypto.createPrivateKey() (elles lisent PEM, DER, JWK et même le point SEC1 nu) et conservez le KeyObject obtenu au lieu de réanalyser des chaînes.

Pour contrôler une signature produite ailleurs, collez-la avec la clé dans le vérificateur de signatures ; pour voir quels comportements par défaut entrent en conflit avec une autre pile, choisissez les deux côtés dans la matrice d’interopérabilité des bibliothèques.

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/node-crypto/run.sh a exécuté Node.js node:crypto 24.22.0 sur OpenSSL 3.5.8 dans l’image de conteneur node:24.22.0-bookworm-slim 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

    crypto.sign() renvoie une SEQUENCE ASN.1 DER, généralement de 70 à 72 octets sur P-256. Avec dsaEncoding: "ieee-p1363" dans l’objet clé, elle renvoie à la place la forme r‖s fixe de 64 octets ; l’option doit être définie côté signature et côté vérification.

    Relevé par la vérification
    fact ecdsa-signature-encoding = der
    evidence ecdsa-signature-encoding: default output parses as DER: true; ieee-p1363 output length 64
  • Low-S à la signature

    either

    Les signatures ne sont pas normalisées : parmi 64 signatures produites lors des essais, au moins une avait un s dans la moitié supérieure de l’intervalle, comme on l’attend d’un nonce aléatoire.

    Relevé par la vérification
    fact ecdsa-low-s-sign = either
    evidence ecdsa-low-s-sign: high-S seen among 64 signatures
  • High-S à la vérification

    accepts-high-s

    crypto.verify() a accepté une signature dont s avait été remplacé par n − s, en DER comme en forme brute. Seul un pair qui applique strictement la règle low-S la refusera.

    Relevé par la vérification
    fact ecdsa-low-s-verify = accepts-high-s
  • Nonce ECDSA

    random

    Nonce aléatoire : signer deux fois le même message avec la même clé a donné des octets différents, et la clé de test de la RFC 6979 n’a pas reproduit la signature de la RFC. Node.js n’offre aucune option ECDSA déterministe.

    Relevé par la vérification
    fact ecdsa-nonce = random
    evidence ecdsa-nonce: same input twice gives identical bytes: false; r equals RFC 6979 A.2.5: false
  • Ce que l’appel de signature hache

    hashes-message

    Les données sont hachées avec l’algorithme que vous indiquez : la signature a été vérifiée sur le SHA-256 du message dans une implémentation indépendante. Aucun appel ne signe un condensat précalculé : passez donc le message lui-même.

    Relevé par la vérification
    fact ecdsa-prehash = hashes-message
    evidence ecdsa-prehash: signature verifies against SHA-256(message) in @noble/curves: true
  • Fonction de hachage par défaut

    sha256

    Avec null ou undefined comme algorithme, la signature avec une clé EC utilise SHA-256, quelle que soit la courbe. Indiquez explicitement la fonction de hachage pour les clés P-384 et P-521.

    Relevé par la vérification
    fact ecdsa-default-hash = sha256
  • Signature ECDSA mal formée

    returns-false

    Une signature r‖s brute là où DER est attendu, du DER là où la forme brute est attendue et de simples octets sans signification font tous renvoyer false à crypto.verify() ; aucune exception n’est levée, si bien qu’un problème de format ressemble à une signature invalide.

    Relevé par la vérification
    fact ecdsa-malformed-signature = returns-false
  • Courbes ECDSA

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

    La génération de clés et un aller-retour signature-vérification ont réussi avec P-224, P-256, P-384, P-521, secp256k1 et les courbes Brainpool r1. crypto.getCurves() liste les noms OpenSSL, comme prime256v1 pour P-256.

    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 signent via crypto.sign(null, data, key) ; Ed448 accepte aussi une option context.

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

    ML-KEMML-DSASLH-DSA

    ML-KEM-512/768/1024 (avec crypto.encapsulate() et crypto.decapsulate()), ML-DSA-44/65/87 et SLH-DSA ont tous fonctionné dans Node.js 24.22.0, qui embarque OpenSSL 3.5.8.

    Relevé par la vérification
    fact pqc = ML-KEM, ML-DSA, SLH-DSA
  • Formats de clé publique

    spki-pemspki-derx509jwksec1-uncompressedsec1-compressed

    crypto.createPublicKey() a lu du SPKI en PEM et en DER, un certificat X.509 en PEM, une JWK et, avec format: "raw-public", le point SEC1 nu, non compressé ou compressé. Un x‖y de 64 octets sans le préfixe 0x04 a été rejeté.

    Relevé par la vérification
    fact public-key-formats = spki-pem, spki-der, x509, jwk, sec1-uncompressed, sec1-compressed
  • Formats de clé privée

    pkcs8-pempkcs8-derencrypted-pkcs8sec1-pemsec1-derjwkraw-scalar

    crypto.createPrivateKey() a lu du PKCS#8 en PEM et en DER, du PKCS#8 chiffré par phrase de passe, une EC PRIVATE KEY SEC1 en PEM et en DER, une JWK avec d et, avec format: "raw-private", le scalaire nu de 32 octets.

    Relevé par la vérification
    fact private-key-formats = pkcs8-pem, pkcs8-der, encrypted-pkcs8, sec1-pem, sec1-der, jwk, raw-scalar

Utilisation

Signer et vérifier avec P-256 en indiquant la fonction de hachage et l’encodage de la signature des deux côtés, ici r‖s brut pour un pair WebCrypto ou JOSE.
import { generateKeyPairSync, sign, verify } from "node:crypto";

const { privateKey, publicKey } = generateKeyPairSync("ec", { namedCurve: "P-256" });
const message = Buffer.from("order #1042 approved");

// Name the hash and the encoding explicitly, on both sides:
// "der" for OpenSSL, Java and Go peers, "ieee-p1363" (raw r||s) for WebCrypto and JOSE.
const signature = sign("sha256", message, { key: privateKey, dsaEncoding: "ieee-p1363" });
const ok = verify("sha256", message, { key: publicKey, dsaEncoding: "ieee-p1363" }, signature);

console.log("signature bytes:", signature.length);
console.log("verified:", ok);

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

Commande
node usage.mjs
La sortie doit correspondre à cette expression régulière
signature bytes: 64\nverified: true

Bibliothèques