使用AES-GCM+Base64实现JS加密PHP解密失败求助
AES-GCM跨JS加密/PHP解密失败排查
我尝试用AES-GCM加密算法结合Base64编码在JS中加密数据,再在PHP中解密,但解密始终返回false,且无任何openssl错误。已确认前后端传输的数据一致,Laravel的composer.json中也添加了"ext-openssl": "*",请问哪里操作有误?
前端JS加密方法
async function encryptData(data, key) { const encoder = new TextEncoder(); const encodedData = encoder.encode(data); // Ensure the key is 256 bits (32 bytes) if (key.length !== 32) { throw new Error('AES key must be 256 bits (32 bytes)'); } const cryptoKey = await crypto.subtle.importKey('raw', new TextEncoder().encode(key), { name: 'AES-GCM' }, false, ['encrypt']); const iv = crypto.getRandomValues(new Uint8Array(12)); const encrypted = await crypto.subtle.encrypt({ name: 'AES-GCM', iv }, cryptoKey, encodedData); // Concatenate IV and encrypted data and convert to base64 const combinedData = new Uint8Array(iv.length + encrypted.byteLength); combinedData.set(iv); combinedData.set(new Uint8Array(encrypted), iv.length); // Convert to base64 using btoa const base64String = btoa(String.fromCharCode.apply(null, combinedData)); return base64String; // Return the base64 string without URL encoding }
前端JS提交加密数据代码
async function submitData() { var licence = $('#licence').val(); const encryptionKey = '12345678901234567890123456789012'; try { let encryptedLicence = await encryptData(licence, encryptionKey); var jsonData = { licence: encryptedLicence }; var queryParams = Object.keys(jsonData) .map(key => encodeURIComponent(key) + '=' + encodeURIComponent(jsonData[key])) .join('&'); var targetUrl = 'submit?' + queryParams; window.location.href = targetUrl; } catch (error) { console.error('Error encrypting data:', error.message); } }
后端PHP接收数据代码
public function formData(Request $request){ $key = '12345678901234567890123456789012'; $data=$request->licence; $decryptedProduct = self::decryptingMyData($data, $key); dd($decryptedProduct); }
后端PHP解密方法
public function decryptingMyData($encryptedData, $key) { // URL-decode the received data $receivedData = urldecode($encryptedData); $decodedData = base64_decode($receivedData); $iv = substr($decodedData, 0, 12); $encryptedText = substr($decodedData, 12); $decrypted = openssl_decrypt($encryptedText, 'aes-256-gcm', $key, OPENSSL_RAW_DATA, $iv); if ($decrypted === false) { // Decryption failed $opensslError = openssl_error_string(); dd($decrypted ,$opensslError); return decrypted; } return $decrypted; }
问题原因与修正方案
核心问题
- AES-GCM认证标签未处理:JS的
crypto.subtle.encrypt在AES-GCM模式下会自动生成16字节的认证标签,并附加在密文末尾。但PHP的openssl_decrypt在GCM模式下需要单独传入该标签才能完成解密验证,原代码未拆分标签导致验证失败。 - 多余的URL解码:Laravel的Request对象已经自动处理了URL解码,手动调用
urldecode会破坏Base64结构(比如把+转成空格)。 - 语法错误:解密失败分支中
return decrypted;是未定义变量,属于语法错误。
修正后的PHP解密方法
public function decryptingMyData($encryptedData, $key) { // Laravel已自动处理URL解码,无需手动调用urldecode $decodedData = base64_decode($encryptedData); // 拆分IV(12字节)、密文、认证标签(16字节) $iv = substr($decodedData, 0, 12); $encryptedText = substr($decodedData, 12, -16); // 提取中间的密文部分 $tag = substr($decodedData, -16); // 提取最后16字节作为认证标签 // 传入标签参数完成GCM解密验证 $decrypted = openssl_decrypt($encryptedText, 'aes-256-gcm', $key, OPENSSL_RAW_DATA, $iv, $tag); if ($decrypted === false) { $opensslError = openssl_error_string(); dd($decrypted, $opensslError); return false; } return $decrypted; }
内容的提问来源于stack exchange,提问作者Gabriel Rogath
相关产品推荐
相关产品推荐

