エラーメッセージ
TypeError: Failed to execute 'sign' on 'SubtleCrypto': EcdsaParams: hash: Missing or not an AlgorithmIdentifier
別の表記
- TypeError: Failed to execute 'verify' on 'SubtleCrypto': EcdsaParams: hash: Missing or not an AlgorithmIdentifier
- TypeError: Member EcdsaParams.hash is required and must be an instance of (object or DOMString)
- NotSupportedError: Failed to execute 'sign' on 'SubtleCrypto': EcdsaParams: hash: Algorithm: Unrecognized name
上記は Chromium のテキストです(`verify` ではメソッド名が変わります)。Safari/WebKit は「Member EcdsaParams.hash is required and must be an instance of (object or DOMString)」、Firefox は汎用的な `SyntaxError`(「An invalid or illegal string was specified」)を報告します。
WebCrypto SubtleCrypto.sign / verify with ECDSA — algorithm parameter without hash (Chromium / WebKit)
このエラーの意味
WebCrypto の ECDSA では、ハッシュ関数を鍵ごとではなく操作ごとに指定します。`sign` と `verify` には `{ name: "ECDSA", hash: "SHA-256" }` が必要です。この呼び出しでは `hash` のないアルゴリズムオブジェクト(たいていは鍵生成時の `{ name, namedCurve }` オブジェクト)が渡されたため、ブラウザーは署名の前にパラメーターを拒否しました。(RSA 鍵は逆で、ハッシュはインポート時に固定されます。)`"SHA256"` のようにハッシュ名の綴りを誤った場合は、代わりに、認識できない名前に関する `NotSupportedError` で失敗します。
発生する理由
鍵生成用のパラメーターを sign/verify でも再利用している
よくある`ALG = { name: "ECDSA", namedCurve: "P-256" }` という定数を 1 つ用意してどこにでも渡す方法は、`generateKey` と `importKey` では動作しますが、`sign` には `hash` が必要です。
ハッシュ名が JOSE や OpenSSL の表記になっている
よくあるWebCrypto での名前は `SHA-256`、`SHA-384`、`SHA-512`(ハイフンあり)です。ハイフンのない `SHA256` や `sha256`、あるいは `ES256` は認識されません。
鍵がハッシュを保持していると想定している
ときどきRSA から移植したコード(`RSASSA-PKCS1-v1_5` の鍵はインポート時のパラメーターに `hash` を持ちます)は、ECDSA の鍵も同じだと想定していますが、そうではありません。
修正方法
- 1.
鍵のパラメーターと操作のパラメーターを分ける
鍵の作成用と署名用に、別々のオブジェクトを定義してください。JWT の ES256/ES384/ES512 では、それぞれ SHA-256/384/512 を P-256/384/521 と組み合わせて使います。
js const KEY_ALG = { name: "ECDSA", namedCurve: "P-256" }; // generateKey / importKey const SIGN_ALG = { name: "ECDSA", hash: "SHA-256" }; // sign / verify (ES256) const pair = await crypto.subtle.generateKey(KEY_ALG, false, ["sign", "verify"]); const signature = await crypto.subtle.sign(SIGN_ALG, pair.privateKey, data); // 64 bytes, raw r||s const ok = await crypto.subtle.verify(SIGN_ALG, pair.publicKey, signature, data);