如何将 AES 解密从 Javascript 转换为 php

How to convert AES Decrypt from Javascript to php

我正在使用以下脚本:-

<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/3.1.2/rollups/aes.js"></script>

和运行在控制台上如下所示:-

JSON.parse(CryptoJS.AES.decrypt("U2FsdGVkX1+S8UNrljj2STY8bBrYmr1qUbD2GYuJgIja1rzXY2y4BBkTf9GQxUGNyfRxP/BxiGIU7EFjnA2nTrM06ySr9bJySTjDDTqlDnY=", "87434313.47913419").toString(CryptoJS.enc.Utf8))

它成功解密了密钥大小为 17 的 AES,但是当我 运行 它使用 PHP 和 phpseclib 时,它抛出错误 Key of size 17 not supported

$data="U2FsdGVkX1+S8UNrljj2STY8bBrYmr1qUbD2GYuJgIja1rzXY2y4BBkTf9GQxUGNyfRxP/BxiGIU7EFjnA2nTrM06ySr9bJySTjDDTqlDnY=";
$key ="87434313.47913419";
$cipher = new AES('ecb');
$cipher->setKey($key);
echo $cipher->decrypt(base64_decode($data));

我做错了什么?感谢任何帮助!!!

错误消息对我来说似乎相当直截了当:该库不支持您使用的密钥大小。事实上,您似乎只粘贴了消息的一部分,继续列出支持的密钥大小:https://github.com/phpseclib/phpseclib/blob/7e38313802b62606cf27ddf573a7c47e88b5d33f/phpseclib/Crypt/AES.php#L118

'Key of size ' . strlen($key) . ' not supported by this algorithm. Only keys of sizes 16, 24 or 32 supported'

所以你的问题不是理解 PHP 能做什么,而是理解 JS 对你的输入做了什么。

线索是 in the CryptoJS docs:

CryptoJS supports AES-128, AES-192, and AES-256. It will pick the variant by the size of the key you pass in. If you use a passphrase, then it will generate a 256-bit key.

因此库同意存在三个有效的 密钥长度 ,但支持传入一个“密码”,而不是将用于 生成 [=27] =] 一把钥匙。没有记录它用于执行此操作的确切算法;大概它使用了一些具有固定参数的密钥派生函数,因此相同的密码短语将始终产生相同的密钥。如果你真的需要模拟它,你需要通过 the source code.

进行追踪

如果您只需要与两个库都兼容的代码,请生成一个支持长度之一的随机密钥,然后在两个地方都使用它。

中所述,在 AES 上下文中,CryptoJS 可以同时处理密钥(16、24 或 32 字节)或密码。这取决于CryptoJS.AES.encrypt()/decrypt()中第二个参数的类型。 WordArray 被解释为密钥,字符串被解释为密码。

在当前情况下,传递了一个字符串,因此将其作为密码处理。 CryptoJS 在加密期间生成一个 8 字节的盐,并从盐和密码派生出一个 32 字节的密钥和一个 16 字节的 IV。密钥派生函数是 OpenSSL 函数 EVP_BytesToKey(),它还需要摘要和迭代计数,CryptoJS 使用值 MD5 和 1。

PHP 中 EVP_BytesToKey() 为 AES-256/CBC 创建 key/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 格式,它由 Salted__ 的 ASCII 编码后跟 8 字节盐和实际密文组成。发布的密文是这个值的Base64编码。解密时,盐和实际密文必须分开:

// Separate salt and ciphertext
$dataB64 = 'U2FsdGVkX1+S8UNrljj2STY8bBrYmr1qUbD2GYuJgIja1rzXY2y4BBkTf9GQxUGNyfRxP/BxiGIU7EFjnA2nTrM06ySr9bJySTjDDTqlDnY=';
$data = base64_decode($dataB64);
$salt = substr($data, 8, 8);
$ciphertext = substr($data, 16);

现在密钥和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,并使用这些值进行加密。