
本文详解如何在不修改原有 javascript 加密逻辑的前提下,使用 php 正确解密由 cryptojs.aes.encrypt 生成的密文,重点解决密钥派生、iv 与盐值传递、加密模式匹配等核心兼容性问题。
本文详解如何在不修改原有 javascript 加密逻辑的前提下,使用 php 正确解密由 cryptojs.aes.encrypt 生成的密文,重点解决密钥派生、iv 与盐值传递、加密模式匹配等核心兼容性问题。
CryptoJS 默认的 AES.encrypt("Message", "Key") 并非简单使用原始密钥进行 AES 加密,而是采用 OpenSSL 兼容的 PBKDF2 衍生逻辑(实际为多次 MD5 迭代),并自动生成随机 IV 和 Salt,最终将 ciphertext、IV、salt 以 Base64/Hex 混合格式序列化为 JSON 字符串。而直接调用 openssl_decrypt(..., 'aes-128-ctr', 'Key') 失败的根本原因在于:密钥长度不匹配、IV 缺失、未还原 Salt 衍生过程、且加密模式(默认 CBC)与 PHP 调用模式(CTR)不一致。
要实现无缝互通,需在 JS 端显式控制序列化格式,在 PHP 端严格复现 CryptoJS 的密钥派生(OpenSSL 兼容的 3 轮 MD5 扩展)和解密流程。以下是完整、经过验证的双向兼容方案:
✅ JavaScript 端:自定义 JSON 格式序列化(无需修改加密逻辑)
const CryptoJSAesJson = {
stringify: function (cipherParams) {
const j = { ct: cipherParams.ciphertext.toString(CryptoJS.enc.Base64) };
if (cipherParams.iv) j.iv = cipherParams.iv.toString(CryptoJS.enc.Hex);
if (cipherParams.salt) j.s = cipherParams.salt.toString(CryptoJS.enc.Hex);
return JSON.stringify(j);
},
parse: function (jsonStr) {
const j = JSON.parse(jsonStr);
const cipherParams = CryptoJS.lib.CipherParams.create({
ciphertext: CryptoJS.enc.Base64.parse(j.ct)
});
if (j.iv) cipherParams.iv = CryptoJS.enc.Hex.parse(j.iv);
if (j.s) cipherParams.salt = CryptoJS.enc.Hex.parse(j.s);
return cipherParams;
}
};
// 加密(保持原有密钥字符串,自动处理 IV/Salt)
const encrypted = CryptoJS.AES.encrypt("Hello, World!", "MySecretKey", {
format: CryptoJSAesJson
}).toString();
// → 示例输出: {"ct":"...","iv":"...","s":"..."}
console.log(encrypted); // 存入数据库或传输⚠️ 注意:CryptoJS.AES.encrypt 默认使用 AES-256-CBC 模式,密钥通过 EVP_BytesToKey 类似逻辑(3 轮 MD5)派生出 32 字节 AES-256 密钥 + 16 字节 IV。务必确保 PHP 端使用 'aes-256-cbc'(而非 aes-128-ctr)并正确还原该派生过程。
✅ PHP 端:精准复现 CryptoJS 密钥派生与解密
function cryptoJsAesDecrypt($passphrase, $jsonString) {
$jsondata = json_decode($jsonString, true);
if (!$jsondata || !isset($jsondata['ct'])) {
throw new InvalidArgumentException('Invalid encrypted data format');
}
$salt = hex2bin($jsondata['s'] ?? '');
$ct = base64_decode($jsondata['ct']);
$iv = hex2bin($jsondata['iv'] ?? '');
// CryptoJS 密钥派生:EVP_BytesToKey 兼容实现(3 轮 MD5)
$keyLen = 32; // AES-256 requires 32-byte key
$md5 = [];
$md5[0] = md5($passphrase . $salt, true);
$result = $md5[0];
for ($i = 1; $i < 3; $i++) {
$md5[$i] = md5($md5[$i - 1] . $passphrase . $salt, true);
$result .= $md5[$i];
}
$key = substr($result, 0, $keyLen);
// 使用派生密钥 + IV 解密(CBC 模式,PKCS#7 填充)
$decrypted = openssl_decrypt($ct, 'aes-256-cbc', $key, OPENSSL_RAW_DATA, $iv);
if ($decrypted === false) {
throw new RuntimeException('Decryption failed: ' . openssl_error_string());
}
return $decrypted;
}
// 使用示例
$encryptedJson = '{"ct":"zQZ9Yb...","iv":"a1b2c3...","s":"d4e5f6..."}';
echo cryptoJsAesDecrypt("MySecretKey", $encryptedJson); // 输出: Hello, World!? 关键注意事项
- 密钥与密码短语(passphrase)必须完全一致:CryptoJS 将字符串 "Key" 视为 passphrase,而非 raw key;PHP 必须传入相同字符串。
- IV 和 Salt 必须从 JSON 中完整提取并 hex2bin 转换:遗漏任一字段将导致解密失败。
- 禁止硬编码模式/长度:CryptoJS 默认使用 AES-256-CBC,若 JS 端显式指定 { mode: CryptoJS.mode.CFB } 等,PHP 端需同步调整。
- 安全性提醒:CryptoJS 的默认派生方式(MD5×3)已属弱算法,生产环境建议升级为 CryptoJS.AES.encrypt(..., key, { keySize: 256, iv: ..., mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.Pkcs7 }) 并配合 PBKDF2(如 CryptoJS.PBKDF2)生成密钥,PHP 端对应使用 hash_pbkdf2。
通过以上方案,即可在零改动原有 JS 加密代码的前提下,实现安全、可靠的跨语言 AES 解密。
立即学习“PHP免费学习笔记(深入)”;



















