正如在其他答案 https://stackoverflow.com/a/67809810/9014097,在 AES 的上下文中,CryptoJS 可以处理密钥(16、24 或 32 字节)或密码。这取决于第二个参数的类型CryptoJS.AES.encrypt()/decrypt()
. A WordArray
被解释为密钥,字符串被解释为密码。
在当前情况下,传递了一个字符串,因此将其作为密码进行处理。 CryptoJS 在加密过程中生成 8 字节的盐,并从盐和密码中派生出 32 字节的密钥和 16 字节的 IV。密钥导出函数是OpenSSL函数EVP_BytesToKey() https://www.openssl.org/docs/man1.1.1/man3/EVP_BytesToKey.html,它还需要一个摘要和一个迭代计数,并且 CryptoJS 使用值 MD5 和 1。
一个可能的实施EVP_BytesToKey()
在 PHP 中为 AES-256/CBC 创建密钥/IV 对是:
// from: https://gist.github.com/ezimuel/67fa19030c75052b0dde278a383eda1b
function EVP_BytesToKey($salt, $password) {
$bytes = '';
$last = '';
// 32 bytes key + 16 bytes IV = 48 bytes.
while(strlen($bytes) < 48) {
$last = hash('md5', $last . $password . $salt, true);
$bytes.= $last;
}
return $bytes;
}
CryptoJS 使用 OpenSSL 格式的密文,它由 ASCII 编码组成Salted__
接下来是 8 字节盐和实际密文。发布的密文是该值的 Base64 编码。在解密过程中,盐和实际密文必须分开:
// Separate salt and ciphertext
$dataB64 = 'U2FsdGVkX1+S8UNrljj2STY8bBrYmr1qUbD2GYuJgIja1rzXY2y4BBkTf9GQxUGNyfRxP/BxiGIU7EFjnA2nTrM06ySr9bJySTjDDTqlDnY=';
$data = base64_decode($dataB64);
$salt = substr($data, 8, 8);
$ciphertext = substr($data, 16);
现在 key 和 IV 可以确定如下:
// Derive key and iv
$passphrase = '87434313.47913419';
$keyiv = EVP_BytesToKey($salt, $passphrase);
$key = substr($keyiv, 0, 32); // hex encoded: e8db19b984ed9196fff1ce9150b73eafc4cb13abe69e6dcc1ea1528dd88982ff
$iv = substr($keyiv, 32, 16); // hex encoded: 47e26ab2bf3b66eda871d4929cc91029
并且可以解密实际的密文,例如使用 phpseclib:
use phpseclib\Crypt\AES;
$cipher = new AES('cbc');
$cipher->setKey($key);
$cipher->setIV($iv);
$plaintext = $cipher->decrypt($ciphertext);
echo json_decode($plaintext); // https://xcdn-209.bato.to/7002/32e/60af5c1a22f39459ededde23/
由于CryptoJS采用OpenSSL格式,因此密文兼容OpenSSL,也可以如下解密:
openssl enc -d -aes-256-cbc -p -pass pass:87434313.47913419 -md md5 -A -a -in <file containing the U2FsdGVk...>
注意EVP_BytesToKey()
被认为是不安全的。更安全的方法是使用可靠的密钥导出函数,如 Argon2 或 PBKDF2(CryptoJS 也支持后者)来导出密钥和 IV,并使用这些值执行加密。