window.crypto.subtle.decrypt抛出OperationError问题的排查与解决
前端RSA-OAEP解密会话密钥时抛出OperationError的解决方法
问题描述
开发会话加密方案时,前端调用window.crypto.subtle.decrypt使用私钥解密会话密钥时,抛出了OperationError。
相关代码
前端密钥生成代码
// 生成密钥的方法 static async generateKeysForUser(): Promise<{ publicKey: string, privateKey: string }> { const keyPair = await window.crypto.subtle.generateKey( { name: "RSA-OAEP", modulusLength: 2048, publicExponent: new Uint8Array([1, 0, 1]), // 65537 hash: "SHA-1", }, true, ["encrypt", "decrypt"] ); const publicKey = await window.crypto.subtle.exportKey("jwk", keyPair.publicKey); const privateKey = await window.crypto.subtle.exportKey("jwk", keyPair.privateKey); const publicKeyStr = JSON.stringify(publicKey); const privateKeyStr = JSON.stringify(privateKey); await this.storeKeys(privateKeyStr, publicKeyStr); return publicKey; }
后端会话密钥加密代码
// 后端生成会话密钥的方法 public static function generateSessionKey($length = 32) { return bin2hex(random_bytes($length)); // 32字节 = 256位 } public static function jsonToPem($publicKeyJson) { $jwkObject = new JWK($publicKeyJson); $rsaKey = RSAKey::createFromJWK($jwkObject); return $rsaKey->toPEM(); } public static function encryptSessionKey($sessionKey, $publicKeyJson): string { $publicKeyPem = self::jsonToPem($publicKeyJson); $publicKey = openssl_pkey_get_public($publicKeyPem); openssl_public_encrypt($sessionKey, $encryptedSessionKey, $publicKey); return base64_encode($encryptedSessionKey); }
前端解密代码
// 前端解密会话密钥的方法 private static async importKey(jwk: JsonWebKey, keyType: 'private' | 'public'): Promise<CryptoKey> { console.log(jwk); let operation = keyType == 'private' ? 'decrypt' : 'encrypt'; console.log(keyType, operation); let result = await window.crypto.subtle.importKey( "jwk", jwk, { name: "RSA-OAEP", hash: "SHA-1" }, true, [operation] ); console.log(result); return result; } static async decryptSessionKey(encryptedSessionKeyBase64: string, jwk: JsonWebKey): Promise<ArrayBuffer | null> { try { let privateKey = await this.importKey(jwk, "private"); const encryptedSessionKey = Uint8Array.from(atob(encryptedSessionKeyBase64), c => c.charCodeAt(0)); console.log('Encrypted Session Key (Uint8Array):', encryptedSessionKey); console.log('Private Key:', privateKey); const result = await window.crypto.subtle.decrypt( { name: "RSA-OAEP" }, privateKey, encryptedSessionKey ); console.log('Decrypted Result (ArrayBuffer):', result); return result; } catch (e) { console.error('Decryption failed:', e); return null; } }
排查与解决方案
已确认前后端密钥一致,尝试将哈希算法从SHA-256改为SHA-1后问题仍存在,最终定位问题出在填充方式不匹配:
前端使用的RSA-OAEP算法默认使用OAEP填充,而PHP的openssl_public_encrypt函数默认使用PKCS#1 v1.5填充,两者不兼容导致解密失败。
修改后端加密代码,在openssl_public_encrypt中添加OPENSSL_PKCS1_OAEP_PADDING参数,指定使用OAEP填充即可解决问题:
public static function encryptSessionKey($sessionKey, $publicKeyJson): string { $publicKeyPem = self::jsonToPem($publicKeyJson); $publicKey = openssl_pkey_get_public($publicKeyPem); openssl_public_encrypt($sessionKey, $encryptedSessionKey, $publicKey, OPENSSL_PKCS1_OAEP_PADDING); return base64_encode($encryptedSessionKey); }
内容的提问来源于stack exchange,提问作者Daniel Moreira
相关产品推荐
相关产品推荐

