Web Crypto API支持双向认证,需用ECDSA/P-256生成密钥对(extractable设为true),公钥导入时明确指定usages:["verify"],再通过sign/verify挑战响应及ECDH+HKDF密钥派生实现安全会话。

Web Crypto API 能直接在浏览器中完成非对称密钥生成、签名验证与密钥协商,但「双向认证」不是开箱即用的功能——它必须由你显式构造:双方各自生成密钥对、交换并验证公钥、再用对方公钥加密会话密钥或签名挑战。漏掉任一环节,就只是单向可信,而非双向认证。
如何用 generateKey 创建可用于双向认证的密钥对
必须使用 ECDSA 或 ECDH 算法族(推荐 "ECDSA" + "P-256"),不能用 RSA —— RSA 在 Web Crypto 中不支持同时用于加密和签名的密钥导出限制太多,且 P-256 椭圆曲线在性能、兼容性和密钥尺寸上更适配前端场景。
关键点:
-
extractable必须设为true,否则无法导出公钥供对方验证 -
usages要按用途拆分:私钥用于"sign"和"deriveKey",公钥仅用于"verify"和"deriveKey" - 不要用
"RSA-OAEP"做密钥交换 —— 它不支持密钥派生(deriveKey),而双向认证需要双方能独立派生同一共享密钥
示例调用:
const keyPair = await window.crypto.subtle.generateKey(
{ name: "ECDSA", namedCurve: "P-256" },
true,
["sign", "verify"]
);
为什么 importKey 导入对方公钥后必须指定 usages: ["verify"]
如果不明确声明用途,导入的公钥默认无任何可用操作权限,后续调用 verify() 会直接抛出 "Operation not allowed for specified key usages" 错误。
常见错误现象:
- 公钥字符串是 base64url 编码的 SPKI 格式,但未用
"spki"标识格式类型,导致importKey解析失败 - 传入的公钥数据是 PEM(含
-----BEGIN PUBLIC KEY-----),没先剥离头尾并 base64-decode,直接传给importKey - 虽然成功导入,但
usages写成["encrypt"]或空数组,签名验证始终返回false
正确做法:
利用 BTC/ETH 相关性(β=1.3),在 BTC 大幅波动时交易 ETH 价格层市场。ETH 往往滞后于 BTC 走势,形成错价窗口...
const publicKey = await window.crypto.subtle.importKey(
"spki",
base64UrlToBuffer(publicKeyPemOrSpki),
{ name: "ECDSA", namedCurve: "P-256" },
false,
["verify"]
);
如何用 sign + verify 实现可落地的双向挑战响应
双向认证的本质不是“交换证书”,而是“证明你真持有私钥”。典型流程是:A 发起随机挑战(如 32 字节 ArrayBuffer),B 用自己私钥签名后发回,A 用 B 的公钥验证;然后 B 同样发起挑战,A 签名返回。两次都通过才算双向认证完成。
注意细节:
- 挑战值必须每次唯一(用
crypto.getRandomValues(new Uint8Array(32))),否则重放攻击可绕过 - 签名算法必须用
{ name: "ECDSA", hash: "SHA-256" },不能省略hash字段,否则 Chrome 会静默失败 - 验证时传入的签名数据必须是原始 DER 编码字节(Web Crypto 默认输出就是 DER),不要尝试 hex 或 base64 中转
验证调用示例:
const isValid = await window.crypto.subtle.verify(
{ name: "ECDSA", hash: "SHA-256" },
publicKey,
signature,
challenge
);
为什么不能跳过密钥协商直接传输对称密钥
即使完成了双向签名验证,若后续消息仍用明文传输对称密钥(比如把 AES 密钥用对方公钥加密后发过去),就退化为单向加密通道——因为服务器或中间人只要截获该密文,就能用对方公钥反复解密,无法保证会话密钥的前向安全性。
真正安全的做法是:双方用各自的私钥 + 对方公钥执行 deriveKey(ECDH),得到完全相同的共享密钥,全程无需传输密钥本身。这个过程天然具备前向保密性,且密钥只存在于内存中。
关键约束:
- 双方密钥对必须同曲线(如都用
P-256),否则deriveKey抛"InvalidAccessError" -
deriveKey输出的密钥必须设extractable: false,防止被exportKey导出泄露 - 导出的共享密钥不能直接当 AES 密钥用——需再走一次
deriveKey+HKDF派生出合适长度的密钥
这是最容易被忽略的一环:很多人以为 ECDH 产出的就是最终密钥,其实它只是熵源,必须经 HKDF 才能安全用于 AES-GCM。

















