使用RSA-OAEP密钥对调用SubtleCrypto解密时出现DOMException报错
问题原因
解密失败的核心原因是密文的二进制数据和UTF-8字符串的转换过程发生了不可逆损坏:
subtle.encrypt接口返回的加密结果是ArrayBuffer类型的原始二进制数据,其中包含大量不符合UTF-8编码规则的字节序列- 直接用
TextDecoder('utf-8').decode()转换该二进制数据时,解码器会自动将非法字节替换为U+FFFD替换字符,原始密文数据被永久修改 - 后续用
TextEncoder.encode转回二进制时,得到的已经不是加密输出的原始密文,不符合RSA-OAEP解密的输入要求,因此抛出DOMException异常。该问题和哈希算法选型无关,切换SHA-1无法解决。
解决方案
密文属于非文本二进制数据,不能直接用UTF-8编解码转换,需要选择无损的二进制转可打印字符串的编码方式存储,比如Base64或十六进制编码。以下是修正后的可运行代码:
let encoder = new TextEncoder(); let decoder = new TextDecoder('utf-8'); // 工具函数:ArrayBuffer转Base64字符串 function arrayBufferToBase64(buffer) { const bytes = new Uint8Array(buffer); let binary = ''; for (let i = 0; i < bytes.byteLength; i++) { binary += String.fromCharCode(bytes[i]); } return btoa(binary); } // 工具函数:Base64字符串转ArrayBuffer function base64ToArrayBuffer(base64) { const binary = atob(base64); const bytes = new Uint8Array(binary.length); for (let i = 0; i < binary.length; i++) { bytes[i] = binary.charCodeAt(i); } return bytes.buffer; } // 生成密钥对 let keyPair = await window.crypto.subtle.generateKey( { name: "RSA-OAEP", modulusLength: 4096, publicExponent: new Uint8Array([1, 0, 1]), hash: "SHA-256" }, true, ["encrypt", "decrypt"] ); let publicKey = await crypto.subtle.exportKey('jwk', keyPair.publicKey); let privateKey = await crypto.subtle.exportKey('jwk', keyPair.privateKey); // 加密字符串 let encodedSecret = encoder.encode("MYSECRETVALUE"); let pubcryptokey = await window.crypto.subtle.importKey( 'jwk', publicKey, { name: "RSA-OAEP", hash: "SHA-256" }, false, ["encrypt"] ); let encrypted = await window.crypto.subtle.encrypt( { name: "RSA-OAEP" }, pubcryptokey, encodedSecret ); // 密文转Base64后再存储,避免UTF-8转换损坏 let ct = arrayBufferToBase64(encrypted); // 解密字符串 // 先将Base64密文转回原始二进制数据 let encodedCiphertext = base64ToArrayBuffer(ct); let privcryptokey = await window.crypto.subtle.importKey( 'jwk', privateKey, { name: "RSA-OAEP", hash: "SHA-256" }, false, ["decrypt"] ); console.log("Before decrypt"); let decrypted = await window.crypto.subtle.decrypt( { name: "RSA-OAEP" }, privcryptokey, encodedCiphertext ); console.log("After decrypt"); let pt = decoder.decode(decrypted); console.log(pt);
如果不使用Base64,也可以选择将二进制密文转换为十六进制字符串存储,核心原则是保证密文二进制数据在转换、存储过程中不会被修改。导出的JWK格式密钥本身是标准JSON字符串,可直接存储不需要额外编码。
内容的提问来源于stack exchange,提问作者IanMckay
相关产品推荐
相关产品推荐

