エラーメッセージ
TypeError: "ES256" signatures must be "64" bytes, saw "71"
別の表記
- "ES256" signatures must be "64" bytes, saw "70"
- "ES256" signatures must be "64" bytes, saw "72"
- "ES384" signatures must be "96" bytes, saw "103"
- "ES512" signatures must be "132" bytes, saw "139"
「saw」の後の数値は、ES256 では 70〜72 の間で変わります(DER の長さは r と s の先頭ビットに依存します)。ES384 では 96 バイト、ES512 では 132 バイトが想定されます。このエラーは `JsonWebTokenError` ではなく単なる `TypeError` であるため、jsonwebtoken のエラーを対象にした `instanceof` のチェックでは捕捉されません。
jsonwebtoken / jws / jwa (Node.js) — TypeError from ecdsa-sig-formatter
このエラーの意味
トークンの署名セグメントをデコードすると 71 バイトになりますが、ES256 の JWS 署名はちょうど 64 バイト、つまり RFC 7518 §3.4 で定義された、r と s をそのまま連結した r‖s でなければなりません。`0x30` で始まる 70〜72 バイトの値は、同じ署名を ASN.1 DER でエンコードしたもので、OpenSSL、Java、KMS サービス、Node の `crypto.sign()` がデフォルトで出力する形式です。このトークンは、DER から生の形式への変換を省略した署名者が発行したものです。jsonwebtoken は(ecdsa-sig-formatter を介して)検証を行う前にこれを拒否します。
発生する理由
自作の署名処理が DER を出力している
よくあるJWT を自前で組み立て、`crypto.sign("sha256", data, key)` や `createSign().sign(key)` で署名するコードは、`dsaEncoding: "ieee-p1363"` を指定しない限り、出力が DER 形式になります。標準に準拠した検証者が長さを確認するまでは、トークンはどこでも問題がないように見えます。
KMS や HSM でトークンに署名している
よくあるAWS KMS の `Sign` と Google Cloud KMS の `asymmetricSign` は ECDSA 署名を DER で返すため、これらを使って JWT に署名する場合は、明示的な変換の手順が必要です。(Azure Key Vault と PKCS#11 の `CKM_ECDSA` は最初から生の r‖s を返します。変換する前に、利用しているプロバイダーのドキュメントの記載を確認してください。)
他の言語のデフォルトのエンコーディング
ときどきJava の `SHA256withECDSA`、Python の cryptography の `sign()`、Go の `ecdsa.SignASN1`、OpenSSL CLI は、いずれも DER を出力します。JOSE 向けに作られたライブラリは自動的に変換しますが、汎用的な暗号処理の呼び出しは変換しません。
署名セグメントが壊れている
まれ報告された長さが 70〜72 の範囲にない場合(たとえば「saw 2」)、3 番目のセグメントは異なる方式でエンコードされたのではなく、途中で切れたか置き換えられています。
修正方法
- 1.
node:crypto で生の r‖s を出力させる
`dsaEncoding: "ieee-p1363"` を指定すると、Node は JWS が要求する固定長の形式で出力します。さらに望ましいのは、この処理を代わりに行ってくれる JOSE ライブラリ(jsonwebtoken の `jwt.sign`、jose の `SignJWT`)で署名することです。
js import { sign } from "node:crypto"; const signature = sign("sha256", Buffer.from(signingInput), { key: privateKey, dsaEncoding: "ieee-p1363", // raw r||s: 64 bytes for P-256, as RFC 7518 §3.4 requires }); const token = signingInput + "." + signature.toString("base64url"); - 2.
トークンを組み立てる前に KMS の DER を変換する
`ecdsa-sig-formatter`(jws/jwa を介して jsonwebtoken と一緒にインストールされます)は、DER を JOSE の形式に変換します。DER ⇄ raw 変換ツールは両方の形式について r と s を表示するので、変換が正しいかを確認できます。
js import ecdsaSigFormatter from "ecdsa-sig-formatter"; // AWS KMS and Google Cloud KMS return DER; convert once before assembling the JWS. const derSignature = Buffer.from(kmsResponse.Signature); const joseSignature = ecdsaSigFormatter.derToJose(derSignature, "ES256"); // base64url, 64 bytes decoded const token = signingInput + "." + joseSignature;