You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用window.crypto.subtle验证ES256签名?Node验证成功Web失败

解决Web Crypto ECDSA P-256签名验证失败问题

问题出在签名格式的差异:Node.js的crypto.verify默认支持DER编码的签名,而Web Crypto的subtle.verify对于ECDSA签名,默认期望的是原始格式(即r和s两个32字节值直接拼接),但你当前传入的是DER编码的签名,这就是验证失败的核心原因。

你需要先把DER格式的签名转换成Web Crypto需要的原始格式,以下是修改后的完整代码:

const crypto = require("crypto");
const webcrypto = require("node:crypto").webcrypto;

const derEncodedPublicKey = Buffer.from(
  "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE5/J6xKyJxzOJ85om+jUJUFHMqnpruqXnKx5jKRojB3E1gC29g/kAc6xHunY05IW+gn2oeAdjggnH7a4WQ8/Afg==",
  "base64"
);

const data = new Uint8Array([
  73, 150, 13, 229, 136, 14, 140, 104, 116, 52, 23, 15, 100, 118, 96, 91, 143,
  228, 174, 185, 162, 134, 50, 199, 153, 92, 243, 186, 131, 29, 151, 99, 5, 0,
  0, 0, 0, 182, 173, 217, 158, 122, 216, 45, 140, 214, 44, 204, 209, 62, 118,
  45, 12, 238, 10, 91, 88, 80, 235, 131, 5, 70, 171, 245, 252, 71, 13, 207, 235,
]);

const sig = new Uint8Array([
  48, 68, 2, 32, 58, 26, 13, 251, 116, 195, 219, 77, 90, 1, 64, 38, 54, 249, 56,
  87, 235, 24, 78, 26, 13, 88, 74, 224, 159, 58, 159, 133, 111, 98, 69, 214, 2,
  32, 87, 1, 32, 191, 170, 10, 33, 204, 86, 124, 73, 21, 153, 4, 58, 182, 248,
  175, 144, 80, 146, 173, 247, 205, 36, 51, 59, 221, 212, 133, 107, 118,
]);

// 将DER格式的ECDSA签名转换为原始r+s拼接格式
function derToRawSignature(derSig) {
  // DER格式结构:0x30 (SEQUENCE) + 总长度 + 0x02 (INTEGER) + r长度 + r值 + 0x02 (INTEGER) + s长度 + s值
  let offset = 0;
  if (derSig[offset++] !== 0x30) throw new Error("Invalid DER signature");
  const totalLength = derSig[offset++];
  if (totalLength + 2 !== derSig.length) throw new Error("Invalid DER length");

  // 解析r
  if (derSig[offset++] !== 0x02) throw new Error("Missing r tag");
  const rLength = derSig[offset++];
  const r = derSig.slice(offset, offset + rLength);
  offset += rLength;

  // 解析s
  if (derSig[offset++] !== 0x02) throw new Error("Missing s tag");
  const sLength = derSig[offset++];
  const s = derSig.slice(offset, offset + sLength);
  offset += sLength;

  // 确保r和s都是32字节(P-256的需求),不足的话前面补0
  const padTo32 = (buf) => {
    if (buf.length === 32) return buf;
    if (buf.length > 32) throw new Error("r/s too long");
    const padded = new Uint8Array(32);
    padded.set(buf, 32 - buf.length);
    return padded;
  };

  return new Uint8Array([...padTo32(r), ...padTo32(s)]);
}

function nodeVerify() {
  const nodeKey = crypto.createPublicKey({
    format: "der",
    key: derEncodedPublicKey,
    type: "spki",
  });
  const v = crypto.createVerify("SHA256").update(data);
  return v.verify(nodeKey, sig);
}

async function webVerify() {
  const webkey = await webcrypto.subtle.importKey(
    "spki",
    derEncodedPublicKey,
    {
      name: "ECDSA",
      namedCurve: "P-256",
    },
    false,
    ["verify"]
  );
  // 使用转换后的原始格式签名
  const rawSig = derToRawSignature(sig);
  return webcrypto.subtle.verify(
    {
      name: "ECDSA",
      hash: "SHA-256",
    },
    webkey,
    rawSig,
    data
  );
}

(async () => {
  console.log("Node verify result:", nodeVerify());
  console.log("Web verify result:", await webVerify());
})().catch(console.error);

修改后运行代码,Web Crypto的验证结果会变成true,和Node.js保持一致。

关键说明:

  • DER编码的ECDSA签名包含了ASN.1结构标记(SEQUENCE、INTEGER),而Web Crypto需要的是无额外标记的裸r+s数据。
  • 转换函数中需要确保r和s都是32字节(P-256曲线的密钥长度对应的签名分量长度),如果原始DER中的r/s不足32字节,需要在前面补0(因为DER编码的INTEGER会省略前导零)。

内容的提问来源于stack exchange,提问作者Otto

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.23 06:54:23