Zum Inhalt springen
ecdsa.com
Node.js

Node.js node:crypto

Das in Node.js eingebaute Modul crypto: ECDSA-, EdDSA-, RSA- und Post-Quanten-Signaturen auf Basis des OpenSSL, das jedem Node.js-Release beiliegt – standardmäßig DER-Signaturen, zufällige Nonces und keine Low-S-Richtlinie.
Lizenz
MIT
Geprüfte Version
24.22.0 ·
Quellcode
github.com/nodejs/node
Dokumentation
nodejs.org/api/crypto.html

Kompatibilität auf einen Blick

ECDSAEdDSAPost-Quanten-Verfahren

ECDSA-Kurven
P-224P-256P-384P-521secp256k1brainpoolP256r1brainpoolP384r1brainpoolP512r1
EdDSA
Ed25519Ed448
Post-Quanten-Algorithmen
ML-KEMML-DSASLH-DSA
Formate öffentlicher Schlüssel
spki-pemspki-derx509jwksec1-uncompressedsec1-compressed
Formate privater Schlüssel
pkcs8-pempkcs8-derencrypted-pkcs8sec1-pemsec1-derjwkraw-scalar

node:crypto ist das Kryptografiemodul, das mit jedem Node.js-Release ausgeliefert wird. Die One-Shot-Funktionen crypto.sign() und crypto.verify() und die Klasse KeyObject decken ECDSA, Ed25519, Ed448, RSA und – Stand Node.js 24.22 – ML-DSA, SLH-DSA und ML-KEM ab. Alles läuft auf dem OpenSSL, das Node.js mitbringt (3.5.8 in Node.js 24.22.0), daher entspricht das Verhalten meist dem der Befehlszeile von OpenSSL – und unterscheidet sich von der WebCrypto-API, die derselbe Prozess als crypto.subtle bereitstellt.

Der Interop-Fehler, auf den du am ehesten stößt, betrifft die Kodierung der Signatur. crypto.sign() liefert ASN.1-DER, während WebCrypto, JWS/JWT, .NET und die meisten Hardware-Wallets die rohe r‖s-Form mit fester Breite verwenden. Eine Signatur in der jeweils anderen Kodierung löst keine Ausnahme aus: crypto.verify() gibt einfach false zurück, was genauso aussieht wie eine gefälschte Signatur. Setze dsaEncoding: "ieee-p1363" im Schlüsselobjekt auf der signierenden und auf der prüfenden Seite oder konvertiere die Bytes mit dem Konverter DER ⇄ Raw.

Zwei weniger auffällige Standardeinstellungen spielen eine Rolle, wenn die Gegenseite streng ist. Signaturen werden nie auf Low-S normalisiert, daher lehnen Verifizierer, die darauf bestehen, etwa libsecp256k1 und @noble/curves 2.x, ungefähr die Hälfte davon ab. Außerdem ist die Nonce zufällig, sodass derselbe Schlüssel und dieselbe Nachricht bei jedem Aufruf andere Bytes ergeben – vergleiche in Tests die Ergebnisse der Prüfung, nie die Bytes der Signatur. Wiederholt ein Signierer jemals eine Nonce, stellt der Nonce-Wiederverwendungs-Checker den Schlüssel wieder her; deshalb braucht eine zufällige Nonce eine intakte Zufallsquelle.

Übergibst du null als Algorithmus, signiert ein EC-Schlüssel stillschweigend mit SHA-256, unabhängig von seiner Kurve; ein P-384-Schlüssel scheitert dann bei jeder Gegenstelle, die SHA-384 erwartet. Nenne den Hash in jedem Aufruf, lade Schlüssel einmal mit crypto.createPublicKey() oder crypto.createPrivateKey() – sie lesen PEM, DER, JWK und sogar den bloßen SEC1-Punkt – und behalte das resultierende KeyObject, statt Strings erneut zu parsen.

Um eine anderswo erzeugte Signatur zu prüfen, füge sie mit dem Schlüssel in den Signatur-Verifizierer ein; welche Standardeinstellungen mit einem anderen Stack kollidieren, siehst du, wenn du im Interop-Cheatsheet für Bibliotheken beide Seiten auswählst.

Fakten und die Prüfungen dahinter

Nichts im Folgenden ist aus einer Dokumentation abgeschrieben. Das Prüfskript content/libraries/verify/node-crypto/run.sh hat Node.js node:crypto 24.22.0 unter OpenSSL 3.5.8 am 20. Oktober 2026 im Container-Image node:24.22.0-bookworm-slim ausgeführt; die Seite muss mit seiner Ausgabe übereinstimmen, und jeder Fakt führt die zugehörigen Zeilen auf.

  • Kodierung von ECDSA-Signaturen

    der

    crypto.sign() liefert eine ASN.1-DER-SEQUENCE, auf P-256 meist 70 bis 72 Byte. Mit dsaEncoding: "ieee-p1363" im Schlüsselobjekt liefert die Funktion stattdessen die feste r‖s-Form mit 64 Byte; die Option muss auf der signierenden und auf der prüfenden Seite gesetzt sein.

    Von der Prüfung aufgezeichnet
    fact ecdsa-signature-encoding = der
    evidence ecdsa-signature-encoding: default output parses as DER: true; ieee-p1363 output length 64
  • Low-S beim Signieren

    either

    Signaturen werden nicht normalisiert: Unter 64 im Test erzeugten Signaturen hatte mindestens eine ein s in der oberen Hälfte des Wertebereichs, wie bei einer zufälligen Nonce zu erwarten.

    Von der Prüfung aufgezeichnet
    fact ecdsa-low-s-sign = either
    evidence ecdsa-low-s-sign: high-S seen among 64 signatures
  • High-S bei der Prüfung

    accepts-high-s

    crypto.verify() akzeptierte eine Signatur, in der s durch n − s ersetzt war, in DER und in roher Form. Einwände erhebt nur eine Gegenstelle, die strikt Low-S verlangt.

    Von der Prüfung aufgezeichnet
    fact ecdsa-low-s-verify = accepts-high-s
  • ECDSA-Nonce

    random

    Zufällige Nonce: Zweimaliges Signieren derselben Nachricht mit demselben Schlüssel ergab unterschiedliche Bytes, und der Testschlüssel aus RFC 6979 reproduzierte die Signatur des RFC nicht. Node.js bietet keine Option für deterministisches ECDSA.

    Von der Prüfung aufgezeichnet
    fact ecdsa-nonce = random
    evidence ecdsa-nonce: same input twice gives identical bytes: false; r equals RFC 6979 A.2.5: false
  • Was der Signaturaufruf hasht

    hashes-message

    Die Daten werden mit dem Algorithmus gehasht, den du angibst – in einer unabhängigen Implementierung bestand die Signatur die Prüfung gegen den SHA-256-Hash der Nachricht. Es gibt keinen Aufruf, der einen vorab berechneten Digest signiert; übergib also die Nachricht selbst.

    Von der Prüfung aufgezeichnet
    fact ecdsa-prehash = hashes-message
    evidence ecdsa-prehash: signature verifies against SHA-256(message) in @noble/curves: true
  • Standard-Hash

    sha256

    Mit null oder undefined als Algorithmus signiert ein EC-Schlüssel unabhängig von der Kurve mit SHA-256. Nenne den Hash für P-384- und P-521-Schlüssel ausdrücklich.

    Von der Prüfung aufgezeichnet
    fact ecdsa-default-hash = sha256
  • Fehlerhafte ECDSA-Signatur

    returns-false

    Eine rohe r‖s-Signatur, wo DER erwartet wird, DER, wo die rohe Form erwartet wird, und schlichter Datenmüll lassen crypto.verify() jeweils false zurückgeben; nichts löst eine Ausnahme aus, sodass ein Formatproblem wie eine falsche Signatur aussieht.

    Von der Prüfung aufgezeichnet
    fact ecdsa-malformed-signature = returns-false
  • ECDSA-Kurven

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

    Mit P-224, P-256, P-384, P-521, secp256k1 und den Brainpool-r1-Kurven ließen sich Schlüssel erzeugen und ein Roundtrip aus Signieren und Prüfen durchführen. crypto.getCurves() listet die Namen von OpenSSL auf, etwa prime256v1 für P-256.

    Von der Prüfung aufgezeichnet
    fact ecdsa-curves = P-224, P-256, P-384, P-521, secp256k1, brainpoolP256r1, brainpoolP384r1, brainpoolP512r1
  • EdDSA

    Ed25519Ed448

    Ed25519 und Ed448 signieren über crypto.sign(null, data, key); Ed448 akzeptiert zusätzlich eine Option context.

    Von der Prüfung aufgezeichnet
    fact eddsa = Ed25519, Ed448
  • Post-Quanten-Algorithmen

    ML-KEMML-DSASLH-DSA

    ML-KEM-512/768/1024 (mit crypto.encapsulate() und crypto.decapsulate()), ML-DSA-44/65/87 und SLH-DSA funktionierten alle in Node.js 24.22.0, das OpenSSL 3.5.8 mitbringt.

    Von der Prüfung aufgezeichnet
    fact pqc = ML-KEM, ML-DSA, SLH-DSA
  • Formate öffentlicher Schlüssel

    spki-pemspki-derx509jwksec1-uncompressedsec1-compressed

    crypto.createPublicKey() las SPKI als PEM und als DER, ein X.509-Zertifikat in PEM, einen JWK und mit format: "raw-public" den bloßen SEC1-Punkt – unkomprimiert oder komprimiert. Ein 64 Byte langes x‖y ohne das Präfix 0x04 wurde abgelehnt.

    Von der Prüfung aufgezeichnet
    fact public-key-formats = spki-pem, spki-der, x509, jwk, sec1-uncompressed, sec1-compressed
  • Formate privater Schlüssel

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

    crypto.createPrivateKey() las PKCS#8 als PEM und DER, mit Passphrase verschlüsseltes PKCS#8, SEC1 EC PRIVATE KEY als PEM und DER, einen JWK mit d und mit format: "raw-private" den bloßen 32-Byte-Skalar.

    Von der Prüfung aufgezeichnet
    fact private-key-formats = pkcs8-pem, pkcs8-der, encrypted-pkcs8, sec1-pem, sec1-der, jwk, raw-scalar

Verwendung

Signiere und prüfe mit P-256 und gib auf beiden Seiten den Hash und die Kodierung der Signatur an – hier rohes r‖s für eine Gegenstelle mit WebCrypto oder 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);

Die Snippet-Testumgebung führt jedes Beispiel unverändert im Container-Image node:24.22.0-bookworm-slim aus, und der Lauf schlägt fehl, wenn die Ausgabe nicht übereinstimmt.

Befehl
node usage.mjs
Ausgabe muss passen (regulärer Ausdruck)
signature bytes: 64\nverified: true

Bibliotheken