本文へスキップ
ecdsa.com
Node.js

Node.js node:crypto

Node.js に組み込まれた crypto モジュールです。各 Node.js リリースに同梱された OpenSSL の上で ECDSA、EdDSA、RSA、ポスト量子の署名を扱い、デフォルトでは署名は DER、nonce はランダムで、low-S のポリシーはありません。
ライセンス
MIT
確認したバージョン
24.22.0 ·
ソースコード
github.com/nodejs/node
ドキュメント
nodejs.org/api/crypto.html

互換性の概要

ECDSAEdDSAポスト量子暗号

ECDSA の曲線
P-224P-256P-384P-521secp256k1brainpoolP256r1brainpoolP384r1brainpoolP512r1
EdDSA
Ed25519Ed448
公開鍵の形式
spki-pemspki-derx509jwksec1-uncompressedsec1-compressed
秘密鍵の形式
pkcs8-pempkcs8-derencrypted-pkcs8sec1-pemsec1-derjwkraw-scalar

node:crypto は、すべての Node.js リリースに含まれる暗号モジュールです。ワンショットの crypto.sign() 関数と crypto.verify() 関数、そして KeyObject クラスで、ECDSA、Ed25519、Ed448、RSA に加え、Node.js 24.22 の時点では ML-DSA、SLH-DSA、ML-KEM も扱えます。すべては Node.js に同梱された OpenSSL(Node.js 24.22.0 では 3.5.8)の上で動作するため、ほとんどの動作は OpenSSL のコマンドラインと同じで、同じプロセスが crypto.subtle として公開する WebCrypto API とは異なります。

最も遭遇しやすい相互運用のバグは、署名のエンコーディングです。crypto.sign() は ASN.1 DER を返しますが、WebCrypto、JWS/JWT、.NET、ほとんどのハードウェアウォレットは固定長の生の r‖s 形式を使います。もう一方のエンコーディングの署名を渡しても例外はスローされず、crypto.verify() は単に false を返すため、偽造された署名とまったく同じように見えます。署名側と検証側の両方で鍵オブジェクトに dsaEncoding: "ieee-p1363" を設定するか、DER ⇄ 生形式の変換ツールでバイト列を変換してください。

相手側が厳格な場合は、目立たない 2 つのデフォルトが問題になります。署名は low-S に正規化されることがないため、libsecp256k1 や @noble/curves 2.x のように low-S を必須とする検証者には、約半分が拒否されます。また、nonce はランダムなので、同じ鍵とメッセージでも呼び出すたびに異なるバイト列になります。テストでは署名のバイト列ではなく、必ず検証結果を比較してください。ある署名者が一度でも nonce を繰り返して使うと、nonce 再利用チェッカーで鍵を復元できてしまいます。だからこそ、ランダムな nonce には健全な乱数源が必要です。

アルゴリズムに null を渡すと、EC 鍵では曲線にかかわらず暗黙のうちに SHA-256 で署名されます。そのため P-384 の鍵では、SHA-384 を想定する相手に対して必ず失敗します。呼び出しのたびにハッシュを明示し、鍵は crypto.createPublicKey() または crypto.createPrivateKey() で一度だけ読み込んでください(PEM、DER、JWK に加え、SEC1 の点そのものも読み込めます)。そして、文字列を解析し直すのではなく、得られた KeyObject を保持してください。

他の環境で作成された署名を確認するには、鍵と一緒に署名検証ツールに貼り付けてください。どのデフォルトが別のスタックと衝突するかを調べるには、ライブラリ相互運用マトリクスで両側を選んでください。

事実とその根拠となるチェック

以下の内容はドキュメントからの転記ではありません。2026年10月20日に、チェック content/libraries/verify/node-crypto/run.sh がコンテナイメージ node:24.22.0-bookworm-slim 内で Node.js node:crypto 24.22.0 を OpenSSL 3.5.8 で実行しました。このページの内容はその出力と一致していなければならず、各事実には該当する出力行を示しています。

  • ECDSA 署名のエンコーディング

    der

    crypto.sign() は ASN.1 DER の SEQUENCE を返し、P-256 では通常 70〜72 バイトです。鍵オブジェクトに dsaEncoding: "ieee-p1363" を指定すると、代わりに固定長 64 バイトの r‖s 形式を返します。このオプションは署名側と検証側の両方で設定する必要があります。

    チェックによる記録
    fact ecdsa-signature-encoding = der
    evidence ecdsa-signature-encoding: default output parses as DER: true; ieee-p1363 output length 64
  • 署名時の low-S

    either

    署名は正規化されません。チェックで作成した 64 個の署名のうち少なくとも 1 つは、ランダムな nonce から予想されるとおり、s が範囲の上半分にありました。

    チェックによる記録
    fact ecdsa-low-s-sign = either
    evidence ecdsa-low-s-sign: high-S seen among 64 signatures
  • 検証時の high-S

    accepts-high-s

    crypto.verify() は、s を n − s に置き換えた署名を DER と生形式の両方で受け入れました。これを拒否するのは、low-S を厳格に求める相手だけです。

    チェックによる記録
    fact ecdsa-low-s-verify = accepts-high-s
  • ECDSA の nonce

    random

    nonce はランダムです。同じ鍵で同じメッセージに 2 回署名すると異なるバイト列になり、RFC 6979 のテスト鍵では RFC の署名を再現できませんでした。Node.js には決定的 ECDSA のオプションはありません。

    チェックによる記録
    fact ecdsa-nonce = random
    evidence ecdsa-nonce: same input twice gives identical bytes: false; r equals RFC 6979 A.2.5: false
  • 署名呼び出しがハッシュする対象

    hashes-message

    データは指定したアルゴリズムでハッシュされます。独立した別の実装で、署名がメッセージの SHA-256 に対して検証できることを確認しました。事前に計算したダイジェストに署名する呼び出しはないため、メッセージそのものを渡してください。

    チェックによる記録
    fact ecdsa-prehash = hashes-message
    evidence ecdsa-prehash: signature verifies against SHA-256(message) in @noble/curves: true
  • デフォルトのハッシュ

    sha256

    アルゴリズムが null または undefined の場合、EC 鍵では曲線にかかわらず SHA-256 で署名されます。P-384 と P-521 の鍵ではハッシュを明示してください。

    チェックによる記録
    fact ecdsa-default-hash = sha256
  • 不正な形式の ECDSA 署名

    returns-false

    DER が想定される場面での生の r‖s 署名、生形式が想定される場面での DER、単なる無意味なデータのいずれでも、crypto.verify() は false を返します。例外は何もスローされないため、形式の問題が不正な署名のように見えます。

    チェックによる記録
    fact ecdsa-malformed-signature = returns-false
  • ECDSA の曲線

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

    P-224、P-256、P-384、P-521、secp256k1、Brainpool の r1 曲線で鍵を生成し、署名から検証までを一通り完了できました。crypto.getCurves() は、P-256 に対する prime256v1 のような OpenSSL の名前を列挙します。

    チェックによる記録
    fact ecdsa-curves = P-224, P-256, P-384, P-521, secp256k1, brainpoolP256r1, brainpoolP384r1, brainpoolP512r1
  • EdDSA

    Ed25519Ed448

    Ed25519 と Ed448 は crypto.sign(null, data, key) で署名します。Ed448 は context オプションも受け付けます。

    チェックによる記録
    fact eddsa = Ed25519, Ed448
  • ポスト量子アルゴリズム

    ML-KEMML-DSASLH-DSA

    OpenSSL 3.5.8 を同梱する Node.js 24.22.0 では、ML-KEM-512/768/1024(crypto.encapsulate() と crypto.decapsulate())、ML-DSA-44/65/87、SLH-DSA のすべてが動作しました。

    チェックによる記録
    fact pqc = ML-KEM, ML-DSA, SLH-DSA
  • 公開鍵の形式

    spki-pemspki-derx509jwksec1-uncompressedsec1-compressed

    crypto.createPublicKey() は、PEM と DER の SPKI、PEM の X.509 証明書、JWK、さらに format: "raw-public" を指定すれば SEC1 の点そのもの(非圧縮形式・圧縮形式)を読み込めました。0x04 のプレフィックスがない 64 バイトの x‖y は拒否されました。

    チェックによる記録
    fact public-key-formats = spki-pem, spki-der, x509, jwk, sec1-uncompressed, sec1-compressed
  • 秘密鍵の形式

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

    crypto.createPrivateKey() は、PEM と DER の PKCS#8、パスフレーズで暗号化された PKCS#8、PEM と DER の SEC1 EC PRIVATE KEY、d を含む JWK、さらに format: "raw-private" を指定すれば 32 バイトのスカラーそのものを読み込めました。

    チェックによる記録
    fact private-key-formats = pkcs8-pem, pkcs8-der, encrypted-pkcs8, sec1-pem, sec1-der, jwk, raw-scalar

使い方

両側でハッシュと署名のエンコーディングを明示して、P-256 で署名と検証を行います。ここでは WebCrypto や JOSE の相手向けに生の r‖s を使います。
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);

スニペットのテストハーネスは、すべてのサンプルをコンテナイメージ node:24.22.0-bookworm-slim 内でそのまま実行し、出力が一致しなければ実行は失敗します。

コマンド
node usage.mjs
一致すべき出力(正規表現)
signature bytes: 64\nverified: true

ライブラリ