用 PHP 加密,用 Javascript 解密 (cryptojs)

IT技术 javascript php encryption cryptojs
2021-02-06 06:04:00

我在基本加密/解密方面遇到问题。我四处寻找一个工作示例,但还没有找到一个工作示例。

- 我将在 php 中加密,用cryptojs 解密以获得一个小的安全层

<script src="http://crypto-js.googlecode.com/svn/tags/3.1.2/build/rollups/aes.js">
<?
$text = "this is the text here";
$key = "encryptionkey";

$msgEncrypted = mcrypt_encrypt(MCRYPT_RIJNDAEL_256, $key, $text, MCRYPT_MODE_CBC, mcrypt_create_iv(mcrypt_get_iv_size(MCRYPT_RIJNDAEL_256, MCRYPT_MODE_ECB), MCRYPT_RAND));
$msgBase64 = trim(base64_encode($msgEncrypted));

echo "<h2>PHP</h2>";
echo "<p>Encrypted:</p>";
echo $msgEncrypted;
echo "<p>Base64:</p>";
echo $msgBase64;
 ?>

<p>AES Decrypt</p>
<script> 
    var key = 'encryptionkey';
    var encrypted = "<?php echo $msgBase64 ?>";
    //tried  var base64decode = CryptoJS.enc.Base64.parse(encrypted); 
    var decrypted = CryptoJS.AES.decrypt(encrypted, key);
    console.log( decrypted.toString(CryptoJS.enc.Utf8) );
</script>

我错过了哪一步?

4个回答

我需要同样的东西,我写了一个简短的库,它适用于 CryptoJS 3.x 和 PHP,支持 openssl。希望这会有所帮助,源代码和示例文件在这里https://github.com/brainfooolong/cryptojs-aes-php

PHP 库

/**
* Decrypt data from a CryptoJS json encoding string
*
* @param mixed $passphrase
* @param mixed $jsonString
* @return mixed
*/
function cryptoJsAesDecrypt($passphrase, $jsonString){
    $jsondata = json_decode($jsonString, true);
    $salt = hex2bin($jsondata["s"]);
    $ct = base64_decode($jsondata["ct"]);
    $iv  = hex2bin($jsondata["iv"]);
    $concatedPassphrase = $passphrase.$salt;
    $md5 = array();
    $md5[0] = md5($concatedPassphrase, true);
    $result = $md5[0];
    for ($i = 1; $i < 3; $i++) {
        $md5[$i] = md5($md5[$i - 1].$concatedPassphrase, true);
        $result .= $md5[$i];
    }
    $key = substr($result, 0, 32);
    $data = openssl_decrypt($ct, 'aes-256-cbc', $key, true, $iv);
    return json_decode($data, true);
}

/**
* Encrypt value to a cryptojs compatiable json encoding string
*
* @param mixed $passphrase
* @param mixed $value
* @return string
*/
function cryptoJsAesEncrypt($passphrase, $value){
    $salt = openssl_random_pseudo_bytes(8);
    $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(json_encode($value), 'aes-256-cbc', $key, true, $iv);
    $data = array("ct" => base64_encode($encrypted_data), "iv" => bin2hex($iv), "s" => bin2hex($salt));
    return json_encode($data);
}

Javascript 库

var CryptoJSAesJson = {
    stringify: function (cipherParams) {
        var j = {ct: cipherParams.ciphertext.toString(CryptoJS.enc.Base64)};
        if (cipherParams.iv) j.iv = cipherParams.iv.toString();
        if (cipherParams.salt) j.s = cipherParams.salt.toString();
        return JSON.stringify(j);
    },
    parse: function (jsonStr) {
        var j = JSON.parse(jsonStr);
        var 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;
    }
}

示例 Javascript

var encrypted = CryptoJS.AES.encrypt(JSON.stringify("value to encrypt"), "my passphrase", {format: CryptoJSAesJson}).toString();
var decrypted = JSON.parse(CryptoJS.AES.decrypt(encrypted, "my passphrase", {format: CryptoJSAesJson}).toString(CryptoJS.enc.Utf8));

示例 PHP

$encrypted = cryptoJsAesEncrypt("my passphrase", "value to encrypt");
$decrypted = cryptoJsAesDecrypt("my passphrase", $encrypted);
@JSON 我想使示例尽可能简短 - 它应该显示基本流程的工作原理。我也不是加密专家,我不确定自定义 IV 和自定义盐应该有什么帮助。Salt 和 IV 存在并且总是不同的,因为 salt 是随机生成的,而 IV 是由密码和随机 salt 产生的。但是当您可以改进该代码时,请在我的 GIT 存储库中进行。
2021-03-14 06:04:00
顺便说一句,只是想补充一下,你刚刚发布这个真是太神奇了......正是我正在寻找的,因为 Mcrypt 是 GPL,其中 OpenSSL 是 Apache/BSD
2021-03-18 06:04:00
@php_coder_3809625 GPL v3 - 我会将它添加到 GitHub 存储库自述文件中。
2021-03-26 06:04:00
看起来不错。但是你为什么选择不使用 IV/Salt?
2021-03-28 06:04:00
@BrainFooLong 上面的代码是否有任何许可证?只是想确保万一我想和我的其他东西一起分发代码。
2021-04-01 06:04:00

安全注意事项:此答案上的代码容易受到选择密文攻击。请参阅此答案以获取安全加密

这是一个使用 PHP 加密字符串并使用 CryptoJS 解密的工作示例。

在 PHP 方面:

使用MCRYPT_RIJNDAEL_128(不是 256)与 AES 配对。这里的 128 是块大小,而不是密钥大小。

也发送IV你需要IV来解密。

$text = "this is the text here";
$key = "encryptionkey";

// Note: MCRYPT_RIJNDAEL_128 is compatible with AES (all key sizes)
$iv = random_bytes(16);

$ciphertext = mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $key, $text, MCRYPT_MODE_CBC, $iv);

echo "iv:".base64_encode($iv)."\n";
echo "ciphertext:".base64_encode($ciphertext)."\n";

以下是测试运行的示例输出:

iv:BMcOODpuQurUYGICmOqqbQ==
ciphertext:ZJAab8YtkRq5TL7uyIR7frM2b3krftJzn1pTqRTAda4=

重要提示:因为我们没有验证我们的密文,解密变得容易受到填充预言机攻击另请参阅:PHP 中的身份验证加密

在 CryptoJS 方面:

您的密钥只有 13 个非常弱的 ASCII 可打印字符。Mcrypt使用字节将密钥填充到有效的密钥大小

keyIV转换word 数组

我用密文作为单词数组解密时运气不佳,因此我将其保留为Base64格式。

CryptoJS = require("crypto-js")

// Mcrypt pads a short key with zero bytes
key = CryptoJS.enc.Utf8.parse('encryptionkey\u0000\u0000\u0000')

iv = CryptoJS.enc.Base64.parse('BMcOODpuQurUYGICmOqqbQ==')

// Keep the ciphertext in Base64 form
ciphertext = 'ZJAab8YtkRq5TL7uyIR7frM2b3krftJzn1pTqRTAda4='

/**
 * DANGER DANGER WILL ROBINSON! <== Stop editing my answer or I will delete it.
 *
 * This example code doesn't demonstrate AUTHENTICATED ENCRYPTION
 * and is therefore vulnerable to chosen-ciphertext attacks.
 *
 * NEVER USE THIS CODE TO PROTECT SENSITIVE DATA!
 */

// Mcrypt uses ZERO padding
plaintext = CryptoJS.AES.decrypt(ciphertext, key, { iv: iv, padding: CryptoJS.pad.ZeroPadding })

// I ran this in nodejs
process.stdout.write(CryptoJS.enc.Utf8.stringify(plaintext))
每个 \u0000 添加一个零字节 0x00,如果字符串是 UTF-16,则添加两个零字节。Mcrypt 必须将密钥填充为 16、24 或 32 个字节。如果这是 ASCII 或 UTF-8,则为 16 个字节。如果是 UTF-16,则为 32 个字节。您应该使用更强的密钥。选择 16、24 或 32 字节的密钥大小,并从加密的强随机数据中生成。
2021-03-16 06:04:00
以 UTF-16 编码的“这是此处的文本”将是 42 个字节,将填充为 48 以进行加密。那些额外的 6 个字节将在末尾显示为三个 UTF-16 字符,这将解释三个有趣的字符。尝试 CryptoJS.pad.NoPadding,并查看那些最后一个字节的二进制值。它们都是 0x00 还是 0x06?
2021-03-20 06:04:00
MCRYPT_RAND不是 CSPRNG。您需要 CBC 模式下的 IV 的 CSPRNG。此外,tonyarcieri.com/...
2021-03-31 06:04:00
您的答案的更新可从修订链接中看到。
2021-04-01 06:04:00
谢谢!不幸的是,我无法让您的示例工作。当我删除“填充:CryptoJS.pad.ZeroPadding”时,它会起作用——但在字符串的末尾添加了“ ”。另外,我想知道如何更改您示例中的加密密钥。“\u0000\u0000\u0000”是什么意思,我如何确定我需要多少?
2021-04-04 06:04:00

不要太深入编码,只需使用base64解码器

在 php 代码上:

$encrypt_val=base64_encode("value");

而在 js 上:

var my_orignal_val = window.atob(passed_val);

这足以满足您的要求。

实际上, var my_orignal_val = window.btoa(passed_val);
2021-03-24 06:04:00

您正在使用两个试图容纳输入的库 - 严格来说 - 无效。Rijndael 需要 16、24 或 32 字节长的随机字节字符串的密钥。您提供一个 13 个字符的字符串。的mcrypt,PHP库,使用字符串(推测UTF8编码)直接作为二进制输入和它的零个垫到所需的32个字节MCRYPT_RIJNDAEL_256另一方面 CryptoJS决定您输入了类似密码的内容,而是使用密钥派生函数来生成 32 字节的密钥

此外,使用的加密算法甚至不匹配。Mcrypt 为 256 位版本使用了原始 Rijndael 的一个很少实现的变体,而 CryptoJS 实现了 Rijndael 提案的广为人知的变体 AES256。MCRYPT_RIJNDAEL_128虽然和 AES128)的 128 位版本是相同的。

您稍后将面临的第三个问题是,Mcrypt 还对被加密的数据使用了一种疯狂的填充方案。由于 Rijndael 是块密码,它只能加密 16、24 或 32 字节的块(取决于变体 - AES 始终使用 16 字节块)。因此必须填充此类数据。Mcrypt 通过添加零以非注入方式完成此操作。如果您只对字符串进行编码,这对您来说不会有太大问题,因为 utf8 编码的字符串从不包含零字节,因此您可以将它们去掉CryptoJS 甚至本机支持)。

解决所有这些问题的最简单方法是避免自己实施任何密码学(无论如何,如果对该主题没有广泛的了解,强烈建议不要这样做)。您能否通过使用 TLS(以前称为 SSL)来加密和验证通道的 https 传输您的敏感信息?

你不是说第二段中的MCRYPT_RIJNDAEL_ 128吗?
2021-04-08 06:04:00