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

node-forge

Digital Bazaar による node-forge は、純粋な JavaScript で書かれた TLS、PKI、PKCS のツールキットです。RSA、PKCS#12、X.509、Ed25519 に対応していますが、ECDSA はまったくサポートしていないため、楕円曲線の鍵や証明書を読み込めません。
ライセンス
BSD-3-Clause OR GPL-2.0
確認したバージョン
1.4.0 ·
ソースコード
github.com/digitalbazaar/forge
ドキュメント
github.com/digitalbazaar/forge#readme

互換性の概要

EdDSA

EdDSA
Ed25519

node-forge は JavaScript による TLS 実装として始まり、現在は主に PKI 関連の機能のために使われています。X.509 証明書と CSR の生成と解析、PKCS#12 バンドルの読み書き、RSA の暗号化と署名、ASN.1 パーサーです。ブラウザーでも Node.js でも同じように動作し、ネイティブコードは含みません。

Ed25519 以外に楕円曲線への対応はありません。forge.pki が公開しているのは rsa と ed25519 だけで、チェックではその結果を確認できました。ECDSA 証明書に certificateFromPem() を使うと Cannot read public key. OID is not RSA. がスローされ、EC 公開鍵に publicKeyFromPem() を使うと Cannot read public key. Unknown OID. がスローされます。同じバイト列は forge 自身の ASN.1 リーダーでは問題なく解析できるにもかかわらずです。実環境の証明書を調べるコードは ECDSA 鍵に頻繁に出会うため、それらには別のパーサーが必要です。node:crypto の X509Certificate や証明書デコーダーなら、どちらの種類の鍵も扱えます。

RSA では、パラメーターがそろえば問題なく相互運用できます。forge が作成した PKCS#1 v1.5 署名と PSS 署名は node:crypto で検証できました。PSS では、ダイジェスト、MGF1 のダイジェスト、ソルト長が両側で一致している必要があり、forge ではサンプルのように 3 つすべてを指定しなければなりません。公開鍵は SPKI、PKCS#1、証明書の PEM から、秘密鍵は PKCS#8、暗号化された PKCS#8、PKCS#1、PKCS#12 から読み込めます。バンドルの中身は PFX インスペクターで開いて確認してください。

同じシードに対して、forge による Ed25519 署名は node:crypto の署名とバイト単位で一致しました。forge の秘密鍵は 64 バイトの展開形式(シードの後に公開鍵が続く形)なので、32 バイトのシードを想定するライブラリには前半だけを渡せば済みます。

署名検証にはパーサーの寛容さに起因する問題の歴史があり、そのうち 1 件は現在も未解決です。2026年9月に公開され、修正版がリリースされていないアドバイザリ GHSA-86w9-cpqp-85rv(CVE-2026-85393)によると、1.4.0 までのバージョンの RSA PKCS#1 v1.5 検証は、DigestInfo のアルゴリズム識別子内にある余分な要素を無視します。そのため、3 のような小さい公開指数を持つ鍵では署名を偽造できてしまいます。チェックではこの寛容さを再現できました。正しくパディングされたブロックのその位置に余分な要素を入れると、forge では検証を通り、node:crypto では拒否されました。修正版がリリースされるまでは、PKCS#1 v1.5 署名を node:crypto か WebCrypto で検証し、指数が 3 の RSA 鍵は避けてください。1.4.0 自体は前回の一連の問題を修正しており(ダイジェストの後ろの余分なフィールドと、正規形でない Ed25519 署名はどちらも拒否されました)、1.3.0 より前のバージョンにはそれ以前の署名偽造のバグ(CVE-2022-24771、CVE-2022-24772)がありました。

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

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

  • ECDSA の曲線

    none

    ECDSA の実装はありません。forge.pki にあるのは rsa と ed25519 で、NIST 曲線や secp256k1 曲線向けのものはありません。

    チェックによる記録
    fact ecdsa-curves = none
    evidence ecdsa-curves: forge.pki members: ed25519, rsa
  • EC 鍵を含む証明書

    rejected

    P-256 の証明書に certificateFromPem() を使うと Cannot read public key. OID is not RSA. がスローされ、EC 公開鍵では Cannot read public key. Unknown OID. がスローされます。

    チェックによる記録
    fact ec-certificate = rejected
    evidence ec-certificate: Cannot read public key. OID is not RSA.
    evidence ec-certificate: the same certificate parses at the ASN.1 level: true
    evidence ec-certificate: publicKeyFromPem on an EC SPKI: Cannot read public key. Unknown OID.
  • EdDSA

    Ed25519

    forge.pki.ed25519 で Ed25519 を扱えます。

    チェックによる記録
    fact eddsa = Ed25519
  • Ed25519 の RFC 8032 準拠

    yes

    同じ 32 バイトのシードに対して、forge は RFC 8032 の決定的な方式が求めるとおり、node:crypto とまったく同じ署名を生成しました。

    チェックによる記録
    fact ed25519-matches-rfc8032 = yes
    evidence ed25519-matches-rfc8032: forge and node:crypto signatures for the same seed are identical: true
  • Ed25519 秘密鍵の構造

    expanded-64

    Ed25519 の秘密鍵は 64 バイトで、シードの後に公開鍵が続きます。

    チェックによる記録
    fact ed25519-private-key = expanded-64
  • 非正規の S を含む Ed25519 署名

    rejected

    S を S + L に置き換えた Ed25519 署名(RFC 8032 が禁じている、展性を利用した形式)は、node:crypto と同様に拒否されました。このチェックは 1.4.0 で追加されました(CVE-2026-33895)。

    チェックによる記録
    fact ed25519-non-canonical-s = rejected
    evidence ed25519-non-canonical-s: forge: false; node:crypto: false
  • ポスト量子アルゴリズム

    none

    ポスト量子アルゴリズムはありません。

    チェックによる記録
    fact pqc = none
  • RSA 署名方式

    PKCS1-v1_5PSS

    SHA-256 で作成した PKCS#1 v1.5 署名と PSS 署名は node:crypto で検証できました。PSS ではソルト長と MGF1 のダイジェストを明示する必要があります。

    チェックによる記録
    fact rsa-signature-schemes = PKCS1-v1_5, PSS
  • PKCS #1 DigestInfo 内の余分な要素

    accepted-in-algorithm-identifier

    DigestInfo の AlgorithmIdentifier 内に余分な要素を含む PKCS#1 v1.5 ブロックは、forge では検証を通りましたが、node:crypto では通りませんでした。ダイジェストの後ろの余分な要素は拒否されました。これは未解決のアドバイザリ GHSA-86w9-cpqp-85rv です。

    チェックによる記録
    fact pkcs1-digestinfo-extra-element = accepted-in-algorithm-identifier
    evidence pkcs1-digestinfo-extra-element: well-formed block: forge true, node:crypto true
    evidence pkcs1-digestinfo-extra-element: extra element inside AlgorithmIdentifier: forge true, node:crypto false
    evidence pkcs1-digestinfo-extra-element: extra element after the digest: forge throws ASN.1 object does not contain a valid RSASSA-PKCS1-v1_5 DigestInfo value., node:crypto false
  • RSA 公開鍵の形式

    spki-pempkcs1x509

    RSA 公開鍵は、SPKI の PEM、PKCS#1 の RSA PUBLIC KEY PEM、証明書から読み込めました。

    チェックによる記録
    fact rsa-public-key-formats = spki-pem, pkcs1, x509
  • RSA 秘密鍵の形式

    pkcs8-pemencrypted-pkcs8pkcs1pkcs12

    RSA 秘密鍵は、PKCS#8 の PEM、暗号化された PKCS#8(decryptRsaPrivateKey)、PKCS#1 の RSA PRIVATE KEY PEM、PKCS#12 から読み込めました。

    チェックによる記録
    fact rsa-private-key-formats = pkcs8-pem, encrypted-pkcs8, pkcs1, pkcs12

使い方

forge で RSA-PSS 署名を行い、同じパラメーターで node:crypto による検証を行ったうえで、EC 鍵を使うとどうなるかを示します。
import { constants, generateKeyPairSync, verify } from "node:crypto";
import forge from "node-forge";

const message = "order #1042 approved";
const { privateKey, publicKey } = generateKeyPairSync("rsa", { modulusLength: 2048 });
const key = forge.pki.privateKeyFromPem(privateKey.export({ type: "pkcs8", format: "pem" }));

// RSA-PSS: SHA-256 for the digest and for MGF1, 32-byte salt. The verifier
// must use the same three parameters.
const sha256 = () => forge.md.sha256.create();
const pss = forge.pss.create({ md: sha256(), mgf: forge.mgf.mgf1.create(sha256()), saltLength: 32 });
const signature = Buffer.from(key.sign(sha256().update(message, "utf8"), pss), "binary");
const options = { key: publicKey, padding: constants.RSA_PKCS1_PSS_PADDING, saltLength: 32 };
console.log("PSS verified by node:crypto:", verify("sha256", Buffer.from(message), options, signature));

// forge has no elliptic-curve support: EC keys and certificates throw.
const ec = generateKeyPairSync("ec", { namedCurve: "P-256" }).publicKey.export({ type: "spki", format: "pem" });
try {
  forge.pki.publicKeyFromPem(ec);
} catch (error) {
  console.log("EC key:", error.message);
}

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

セットアップ
npm init -y >/dev/null && npm install --no-audit --no-fund node-forge@1.4.0
コマンド
node usage.mjs
一致すべき出力(正規表現)
PSS verified by node:crypto: true\nEC key: Cannot read public key\. Unknown OID\.

ライブラリ