Node.js v16.10使用webcrypto导入RSA私钥报ASN1 header too long错误
问题原因
报错的核心是你在Node.js侧自定义的atob实现和浏览器原生atob行为不匹配:
- 浏览器原生
atob会把base64内容解码为单字节二进制字符串,每个字符严格对应1字节原始二进制数据 - 你写的Node.js版本
atob=(text)=>Buffer.from(text,'base64').toString()默认用UTF-8编码转字符串,UTF-8是可变长编码,会将二进制数据中超出ASCII范围的字节识别为多字节字符,导致最终得到的字符串长度和原始数据长度不符,内容也被篡改 - 被篡改的内容转为ArrayBuffer传入
importKey后,ASN.1解析器无法识别无效的PKCS8结构,就抛出了header too long的错误
解决方法
有两种可行的修正方案,任选其一即可:
方案1:对齐浏览器atob行为
修改自定义atob的实现,转字符串时使用单字节的latin1编码,和浏览器行为保持一致:
// 修正atob实现 atob=(text)=>Buffer.from(text,'base64').toString('latin1') btoa=(text)=>Buffer.from(text,'latin1').toString('base64')
修改后原来的代码其余部分不需要改动即可正常运行。
方案2:省略冗余转换步骤(更推荐)
不需要将base64解码后的内容转字符串再转ArrayBuffer,直接将Buffer转为ArrayBuffer传入接口,性能更高也避免编码问题:
var crypto=require('crypto') var privateKeyStr=`-----BEGIN PRIVATE KEY----- REAL_PRIVATE_KEY_HERE -----END PRIVATE KEY-----` function ab2str(buf) { var buff=new Uint8Array(buf) return Object.getOwnPropertyNames(buff) .map(i=>String.fromCharCode(buff[i])).join('') } let pemHead1="-----BEGIN PRIVATE KEY-----", pemFoot1="-----END PRIVATE KEY-----" // 提取base64内容,移除所有空白字符避免解码错误 const prvKeyBase64 = privateKeyStr .substring(pemHead1.length, privateKeyStr.length - pemFoot1.length) .replace(/\s/g, ''); // 直接转ArrayBuffer,无需经过字符串中转 const prvKeyAb = Buffer.from(prvKeyBase64, 'base64'); (async()=>{ const prvKey=await crypto.webcrypto.subtle.importKey('pkcs8',prvKeyAb,{name:"RSA-OAEP",hash:"SHA-256"},true,['decrypt']) const rsa_decrypt=async(secret_text)=>{ const secretAb = Buffer.from(secret_text, 'base64'); return ab2str(await crypto.webcrypto.subtle.decrypt('RSA-OAEP',prvKey,secretAb)) } })()
内容的提问来源于stack exchange,提问作者The Bomb Squad
相关产品推荐
相关产品推荐

