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