
PKIJS中签名后验证失败,通常是因为密钥导入时指定的算法与签名算法不匹配;将RSA私钥导入时需使用RSASSA-PKCS1-v1_5而非RSA-PSS,才能与PKIJS默认的CMS签名机制兼容。
pkijs中签名后验证失败,通常是因为密钥导入时指定的算法与签名算法不匹配;将rsa私钥导入时需使用`rsassa-pkcs1-v1_5`而非`rsa-pss`,才能与pkijs默认的cms签名机制兼容。
在使用 PKIJS(@peculiar/webcrypto + asn1js + pkijs)实现 CMS 签名与验证时,一个常见却隐蔽的问题是:自行调用 signData() 生成的签名无法通过 SignedData.verify() 验证,而用 OpenSSL 命令行(如 openssl cms -sign)生成的等效签名却能成功验证。这并非签名逻辑或证书加载有误,而是源于 Web Crypto API 密钥导入阶段的算法配置偏差。
? 根本原因:密钥算法与签名机制不匹配
PKIJS 的 SignedData.sign() 方法在内部默认采用 RSASSA-PKCS1-v1_5 签名方案(符合 RFC 5652 CMS 规范),而非 RSA-PSS。当你使用以下方式导入私钥时:
await crypto.importKey("pkcs8", ber, {
name: "RSA-PSS", // ❌ 错误:PSS 与 CMS 默认签名不兼容
hash: "SHA-256",
}, true, ["sign"]);
虽然 RSA-PSS 是现代推荐的抗碰撞性更强的方案,但 PKIJS 的 sign() 方法并未启用 PSS 参数(如 saltLength),也未在 ASN.1 编码中正确标识 PSS 算法标识符(OID 1.2.840.113549.1.1.10)。结果导致:
- 签名计算底层实际仍走 PKCS#1 v1.5 流程;
- 但密钥对象被标记为
RSA-PSS,引发 Web Crypto 内部签名行为异常或不一致; - 最终生成的
SignerInfo.signatureAlgorithm字段与签名值不匹配,致使verify()校验失败(返回false)。
✅ 正确做法:显式使用 RSASSA-PKCS1-v1_5
只需将密钥导入的 name 改为 "RSASSA-PKCS1-v1_5",并保持哈希一致即可:
// ✅ 正确:与 PKIJS CMS 签名完全兼容
await crypto.importKey("pkcs8", ber, {
name: "RSASSA-PKCS1-v1_5",
hash: "SHA-256"
}, true, ["sign"]);
? 补充说明:
RSASSA-PKCS1-v1_5是 Web Crypto 标准名称,对应 OID1.2.840.113549.1.1.1,正是 CMS 中最广泛支持的签名算法。
? 完整修复后的 loadPEM 片段
case "PRIVATE KEY":
for (const ber of bers) {
const key = await crypto.importKey("pkcs8", ber, {
name: "RSASSA-PKCS1-v1_5", // ✅ 关键修正
hash: "SHA-256"
}, true, ["sign"]);
ret.push(key);
}
return ret;
同时确保签名函数无需修改(它已按规范工作):
export async function signData(data: ArrayBuffer, certificate: Certificate, privateKey: CryptoKey): Promise<signeddata> {
const cmsSigned = new SignedData({
encapContentInfo: new EncapsulatedContentInfo({
eContentType: ContentInfo.DATA,
}),
signerInfos: [
new SignerInfo({
sid: new IssuerAndSerialNumber({
issuer: certificate.issuer,
serialNumber: certificate.serialNumber
})
})
],
certificates: [certificate]
});
await cmsSigned.sign(privateKey, 0, "SHA-256", data); // ✅ 自动使用 RSASSA-PKCS1-v1_5 + SHA-256
return cmsSigned;
}</signeddata>
⚠ 注意事项与最佳实践
-
不要混用算法:若后续需支持 RSA-PSS,必须同步改写
sign()调用(传入pssParameters)并手动设置SignerInfo.signatureAlgorithm,但会显著增加复杂度且降低互操作性。 -
证书链验证依赖
trustedCerts:checkChain: true要求trustedCerts至少包含签发该证书的 CA(或自签名根证书),若仅传入终端证书,链验证仍可能失败。 -
数据一致性:验证时传入的
data.buffer必须与签名时完全一致(包括字节顺序、BOM、换行符等),建议用Uint8Array显式比对。 -
调试技巧:可通过
signedData.toSchema().toBER(false)导出 DER 并用openssl asn1parse -inform DER -in sig.der查看signatureAlgorithmOID 是否为1.2.840.113549.1.1.1。
修复后,console.log(ok) 将稳定输出 true,实现端到端可验证的 CMS 签名流程,与 OpenSSL 工具链完全兼容。











