PHP AES-GCM加密后JavaScript解密报错求助
AES-GCM跨语言加解密错误修复方案
问题根源分析
原代码存在三个核心问题:
- PHP端重复拼接Tag:加密逻辑中两次拼接Tag,导致最终数据格式混乱,无法正确拆分
- 二进制Tag与字符串分隔符冲突:GCM的Tag是二进制数据,直接和
||拼接可能包含分隔符,导致解密时分割错误 - Web Crypto API使用错误:原JS代码将密文和Tag拼接后传入解密函数,但Web Crypto要求Tag单独作为参数传递
修复后的完整代码
PHP加密代码
function encode(string $input): string { return base64EncodeSafe(encodeOpenSsl($input)); } function encodeOpenSsl(string $string): string { if (!defined('CRYPT_KEY') || !defined('CRYPT_CIPHER')) { return $string; } // 强制校验算法为AES-GCM if (!str_ends_with(strtolower(CRYPT_CIPHER), 'gcm')) { throw new Exception('Cipher must be AES-GCM type'); } $key = hex2bin(CRYPT_KEY); $ivLength = openssl_cipher_iv_length(CRYPT_CIPHER); // AES-GCM标准IV长度为12字节,强制校验避免兼容问题 if ($ivLength !== 12) { throw new Exception('Invalid IV length for AES-GCM'); } // 生成强随机IV $iv = openssl_random_pseudo_bytes($ivLength, $cstrong); if (!$iv || !$cstrong) { throw new Exception('Failed to generate secure IV'); } // 执行加密,获取二进制密文和Tag $encrypted = openssl_encrypt( $string, CRYPT_CIPHER, $key, OPENSSL_RAW_DATA, $iv, $tag ); if (!$encrypted || empty($tag)) { throw new Exception('Encryption failed'); } // 按固定长度拼接:IV(12字节) + 密文 + Tag(16字节) $encryptedData = $iv . $encrypted . $tag; return base64_encode($encryptedData); } function base64EncodeSafe(string $string): string { return str_replace(['/', '+'], ['_', '-'], base64_encode($string)); }
JavaScript解密代码
async function decode(input) { return await decodeOpenSsl(base64DecodeSafe(input)); } function base64DecodeSafe(string) { // 恢复标准base64格式并补全填充字符 string = string.replace(/_/g, '/').replace(/-/g, '+'); const padLength = (4 - (string.length % 4)) % 4; string += '='.repeat(padLength); return atob(string); } async function decodeOpenSsl(data) { const key = hexStringToByteArray(CRYPT_KEY); const binaryData = new Uint8Array( data.split('').map(c => c.charCodeAt(0)) ); const ivLength = 12; // AES-GCM标准IV长度 const tagLength = 16; // AES-GCM默认128位Tag长度 // 按固定长度拆分IV、密文、Tag const iv = binaryData.slice(0, ivLength); const encrypted = binaryData.slice(ivLength, -tagLength); const tag = binaryData.slice(-tagLength); // 导入原始密钥 const cryptoKey = await crypto.subtle.importKey( 'raw', key, { name: 'AES-GCM' }, false, ['decrypt'] ); // 执行解密,Tag单独传入参数 const decryptedBuffer = await crypto.subtle.decrypt( { name: 'AES-GCM', iv: iv, additionalData: new Uint8Array(), tagLength: 128, tag: tag }, cryptoKey, encrypted ); return new TextDecoder().decode(decryptedBuffer); } function hexStringToByteArray(hexString) { const result = []; for (let i = 0; i < hexString.length; i += 2) { result.push(parseInt(hexString.substring(i, i + 2), 16)); } return new Uint8Array(result); }
关键注意事项
- 确保两端
CRYPT_KEY完全一致:AES-256-GCM对应64位十六进制字符串,AES-128-GCM对应32位十六进制字符串 CRYPT_CIPHER需统一设置为aes-256-gcm或aes-128-gcm- 若使用附加数据(additionalData),两端需保持完全一致
内容的提问来源于stack exchange,提问作者Haidrex
相关产品推荐
相关产品推荐

