将CryptoJS加密逻辑转为PHP后服务端拒绝请求的问题排查
问题描述
我有一套可正常完成数据加解密的CryptoJS代码,将加密逻辑迁移到PHP实现后,发现PHP加密的结果能被JS正常解密,但会被服务端拒绝;而JS加密的结果则可被服务端正常接受。无法访问后端服务,以下是可用的JS加密解密代码及对应的PHP实现代码,请问问题出在哪里?最初怀疑是单双引号的问题,但不清楚后端逻辑。
可用的JS代码
let u = CryptoJS; function decrypt(e) { let t = (e = u.enc.Base64.parse(e)).toString(u.enc.Utf8); t = JSON.parse(t); let a = u.enc.Base64.parse(t.iv); var n = u.AES.decrypt( t.value, u.enc.Utf8.parse([KEY]), { iv: a, mode: u.mode.CBC, padding: u.pad.Pkcs7 } ); return (n = u.enc.Utf8.stringify(n)); } function encrypt(e) { let t = u.lib.WordArray.random(16), a = u.enc.Utf8.parse([KEY]), n = { iv: t, mode: u.mode.CBC, padding: u.pad.Pkcs7 }, l = u.AES.encrypt(e, a, n); (l = l.toString()), (t = u.enc.Base64.stringify(t)); let r = { iv: t, value: l, mac: u.HmacSHA256(t + l, a).toString() }; return ( (r = JSON.stringify(r)), (r = u.enc.Utf8.parse(r)), u.enc.Base64.stringify(r) ); }
原PHP实现代码
function enc($plainData,$secrete_key){ $key = base64_decode($secrete_key); $iv = base64_encode(random_bytes(openssl_cipher_iv_length('AES-256-CBC'))); $value = openssl_encrypt($plainData, 'AES-256-CBC', $secrete_key, 0, base64_decode($iv)); $mac = hash('sha256', $iv.$value.$secrete_key); $json = json_encode(["iv"=>$iv,"value"=>$value,"mac"=>$mac], JSON_UNESCAPED_UNICODE); return base64_encode($json); } $data = [ "start_date" => "2023-07-23 00:00:00", "end_date" => "2023-07-23 23:59:59", "graph_uuid" => "1234567890", "ignore_zero" => true, ]; $enc = enc(json_encode($data),env(KEY))
问题排查与修正
对比JS和PHP代码,存在以下几个关键差异,这些差异是服务端拒绝PHP加密结果的核心原因:
密钥处理逻辑不一致
JS直接将KEY的UTF-8字节作为密钥,未做Base64解码;PHP中先对密钥做Base64解码,加密时却又用原始密钥,逻辑混乱且和JS不符。MAC算法错误
JS使用的是HMAC-SHA256算法,而PHP用的是普通SHA256哈希,二者完全不是同一算法,服务端校验MAC时会失败。AES加密参数不匹配
JS会根据密钥字节长度自动选择AES-128/192/256;PHP硬编码使用AES-256-CBC,若密钥长度不符合32字节,openssl会自动调整,导致加密结果和JS不一致。JSON序列化细节差异
PHP使用JSON_UNESCAPED_UNICODE参数,和JS默认的JSON序列化行为不同,可能导致后续Base64编码结果不一致。
修正后的PHP代码
function enc($plainData, $secret_key){ // 生成16字节随机IV,和JS的WordArray.random(16)逻辑一致 $iv_bytes = random_bytes(16); $iv = base64_encode($iv_bytes); // 密钥处理和JS对齐:取KEY的UTF-8字节 $key = mb_convert_encoding($secret_key, 'UTF-8'); // 根据密钥字节长度自动匹配AES版本 $key_len = strlen($key); $cipher = match(true) { $key_len == 16 => 'AES-128-CBC', $key_len == 24 => 'AES-192-CBC', $key_len == 32 => 'AES-256-CBC', default => throw new Exception("密钥长度无效:必须是16、24或32字节") }; // 加密:结果为Base64编码,和JS的l.toString()一致 $value = openssl_encrypt($plainData, $cipher, $key, 0, $iv_bytes); // 计算HMAC-SHA256,和JS的HmacSHA256(t + l, a)逻辑一致 $mac = hash_hmac('sha256', $iv . $value, $key); // JSON序列化保持默认,和JS的JSON.stringify行为一致 $json = json_encode(["iv" => $iv, "value" => $value, "mac" => $mac]); // 最后转Base64输出,和JS逻辑对齐 return base64_encode($json); } $data = [ "start_date" => "2023-07-23 00:00:00", "end_date" => "2023-07-23 23:59:59", "graph_uuid" => "1234567890", "ignore_zero" => true, ]; $enc = enc(json_encode($data), env('KEY'));
内容的提问来源于stack exchange,提问作者mariotanenbaum
相关产品推荐
相关产品推荐

