错误信息
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`。
原因
把生成密钥的参数复用于签名/验证
常见把 `ALG = { name: "ECDSA", namedCurve: "P-256" }` 存成一个常量并到处传递,对 `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);