【问题标题】:Crypto-js local side to php server sideCrypto-js 本地端到 php 服务器端
【发布时间】:2019-12-05 17:04:43
【问题描述】:

我有一个使用 crypto-Js AES 的应用。模拟工作代码为:

var ciphertext = CryptoJS.AES.encrypt('My_message', 'My_secret_key');
console.log(ciphertext.toString()); 

答案是:

U2FsdGVkX1/Dd3uAr/mdw5lVoBvq0UX5LHnNoX24JAM=

当我尝试在服务器端重现它时,我永远不会得到相同的答案:

$passphrase='My_secret_key';
$value='My_message';
$salt = openssl_random_pseudo_bytes(8);
$salt ='';
$salted = '';
$dx = '';
while (strlen($salted) < 48) {
 $dx = md5($dx.$passphrase.$salt, true);
 $salted .= $dx;
}
$key = substr($salted, 0, 32);
$iv  = substr($salted, 32,16);
$encrypted_data = openssl_encrypt($value, 'aes-256-cbc', $key, true, $iv);
echo base64_encode($encrypted_data);

服务器端答案:

3jSTl1yR55lfTbz7f0o3Yw==

我必须错过一些东西,但不能指出什么。当地的一面不能碰。 欢迎所有帮助

【问题讨论】:

    标签: javascript php cryptography aes


    【解决方案1】:

    如果CryptoJS.AES.encrypt 中的第二个参数作为字符串传递,则它被解释为从中派生实际密钥和IV 的密码短语[1]。这是通过使用迭代计数为 1 的 OpenSSL 函数 EVP_BytesToKey 和 MD5-digest [2] [3] 的功能来实现的(请注意,CryptoJS 不考虑从从 OpenSSL 版本 1.1.0c 开始的 MD5 到 SHA256,[4])。

    CryptoJS.AES.encrypt返回一个CipherParams-object,它封装了密文、key、IV和salt,[5]。此外,CipherParams#toString() 将 OpenSSL 格式的结果作为 Base64 编码的字符串返回。 OpenSSL 格式由一个 16 字节的标头和随后的密文组成。标头以 ASCII 编码字符串 Salted__ 开头,后跟一个 8 字节的盐。每次随机生成盐,并与密码一起使用以导出密钥/IV。这每次都会创建一个不同的密钥/IV。

    PHP 代码在功能上是相同的:Key 和 IV 是通过模拟逻辑从密码短语中派生出来的,每次使用新生成的盐(有关证明,请参见下文)。但是,需要进行一些小的更改:

    • 必须删除以下行:$salt ='';

    • 在当前代码中,仅显示 Base64 编码的密文。对于 OpenSSL 格式的结果的 Base64 编码输出,代码必须改为:

      echo base64_encode('Salted__'.$salt.$encrypted_data); 
      
    • openssl_encrypt 中的第 4 个参数应从 true 更改为 OPENSSL_RAW_DATA。两者功能相同,但OPENSSL_RAW_DATA 的使用更透明。

    JavaScript 和 PHP 代码每次都会生成一个新的 salt,因此会生成不同的密钥和 IV,每次都会更改密文。这就是它应该的方式。由于盐与密文一起存储,因此可以随时使用密码对密文进行解密。

    证明两个代码使用相同的逻辑来推导密钥和 IV: 每次生成的新盐/密文防止直接比较两个代码的结果。为了毫不费力地执行此比较,最好在 PHP 代码中也使用 JavaScript 代码中生成的盐。 JavaScript代码中的盐可以确定为十六进制字符串:

    console.log(ciphertext.salt.toString(CryptoJS.enc.Hex)); 
    

    此盐将在 PHP 代码中使用,而不是随机生成的盐(当然仅用于这一比较):

    $salt = hex2bin('<Salt from JavaScript-Code as hexadecimal string>'); 
    

    现在比较两个输出证明它们是相等的,表明两个代码在功能上是相同的。

    【讨论】:

      【解决方案2】:

      我以前也遇到过你的情况。我们很难在 PHP 和 Java(Android)上获得相同的结果。许多开发人员在 2 家公司工作了很多天。没运气。

      我们最终从 PHP 调用 CryptoJS。如果我没记错的话,Crypto JS 中有一些非标准的东西。我可能是错的,这是不久前的事了。

      通过 PHP 使用 node 调用 CryptoJS 可能是一个更健壮的解决方案。然后您可以使用相同的库来确保兼容性。

      $result = shell_exec('node yourCryptoJSprogram.js');
      

      我们确实达到了通过这种方式可以作为参数传递的数据量的限制。我建议使用 PHP 写入文件并再次使用 NodeJS 读取。

      如果性能出现问题,请考虑运行 Express 服务器并从 PHP 进行 REST 调用。

      如果这个答案不满足您,请考虑使用或复制我编写的这个简单的基于 OpenSSL 的 PHP 库来解决这个问题: https://github.com/io-digital/php-crypto

      【讨论】:

      • 通过 PHP 使用 node 调用 CryptoJS 可能是一个更强大的解决方案。 - 真的不是!我对未来读者的建议是不要使用 CryptoJS - 它的文档很糟糕,并且隐藏了许多导致与此完全相同的问题的重要实现细节。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-03-03
      • 1970-01-01
      • 1970-01-01
      • 2012-05-20
      • 1970-01-01
      相关资源
      最近更新 更多