使用OpenSSL生成ECDSA签名后,Web Crypto API验证失败求助
ECDSA签名Web Crypto API验证失败的原因与解决方法
问题核心
OpenSSL默认生成的ECDSA签名是ASN.1/DER编码格式,而Web Crypto API要求的是IEEE P1363格式(原始r+s字节拼接),这是导致验证失败的根本原因。
格式差异说明
- OpenSSL默认签名格式:DER编码的ASN.1序列,结构为
0x30 [总长度] 0x02 [r长度] r值 0x02 [s长度] s值,其中r和s是可变长度的整数(可能小于32字节)。 - Web Crypto要求的格式:固定64字节的二进制数据,前32字节是r值(不足补前导0),后32字节是s值(同样补前导0)。
解决方案
方案1:修改OpenSSL签名命令,直接生成P1363格式签名
在签名时添加-sigopt ecdsa_scheme:plain参数,让OpenSSL直接输出Web Crypto兼容的格式:
openssl dgst -sha256 -sign ecdsa_private_key.pem -out ecdsa_signature.bin -sigopt ecdsa_scheme:plain data.txt
使用该命令生成的签名,无需修改JS代码即可直接通过Web Crypto验证。
方案2:在JavaScript中转换签名格式
如果无法修改签名生成流程,可以在验证前将DER格式的签名转换为P1363格式,添加以下转换函数:
function derToP1363(signatureDer) { const buffer = new Uint8Array(signatureDer); let offset = 0; // 解析ASN.1 SEQUENCE头部 if (buffer[offset++] !== 0x30) throw new Error("无效的DER签名格式"); let length = buffer[offset++]; if (length & 0x80) { const lenBytes = length & 0x7F; length = 0; for (let i = 0; i < lenBytes; i++) { length = (length << 8) | buffer[offset++]; } } // 提取r值 if (buffer[offset++] !== 0x02) throw new Error("无效的r字段标识"); const rLen = buffer[offset++]; const r = buffer.slice(offset, offset + rLen); offset += rLen; // 提取s值 if (buffer[offset++] !== 0x02) throw new Error("无效的s字段标识"); const sLen = buffer[offset++]; const s = buffer.slice(offset, offset + sLen); offset += sLen; // 将r和s补全为32字节(P-256曲线要求) const fixedR = new Uint8Array(32); fixedR.set(r, 32 - r.length); const fixedS = new Uint8Array(32); fixedS.set(s, 32 - s.length); // 拼接r和s得到P1363格式签名 return new Uint8Array([...fixedR, ...fixedS]); }
然后修改验证代码,转换签名后再验证:
// ... 原有的密钥导入、数据读取代码不变 ... const signature = await readFile("./ecdsa_signature.bin"); // 转换签名格式 const p1363Signature = derToP1363(signature); const isValid = await crypto.subtle.verify( { name: 'ECDSA', hash: { name: 'SHA-256' } }, publicKey, p1363Signature, data ); console.log(isValid); // 现在会返回true
内容的提问来源于stack exchange,提问作者vanilla
相关产品推荐
相关产品推荐

