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

时间:2022-07-28 18:24:26

I'm having trouble with basic encryption/decryption. I've looked all around for a working example but haven't quite found a working example.

我在基本加密/解密方面遇到了麻烦。我到处寻找一个有效的例子,但还没有找到一个有效的例子。

-I will be encrypting in php, decrypting with cryptojs for a small layer of security

-我将用php加密,用加密来解密一小层安全

<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>

Which step am I missing?

我错过了哪一步?

3 个解决方案

#1


43  

I've required the same thing and i wrote a short library that works for CryptoJS 3.x and PHP with openssl support. Hope this helps, source plus example files here https://github.com/brainfoolong/cryptojs-aes-php

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

PHP Lib

/**
* 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 Lib

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;
    }
}

Example 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));

Example PHP

$encrypted = cryptoJsAesEncrypt("my passphrase", "value to encrypt");
$decrypted = cryptoJsAesDecrypt("my passphrase", $encrypted);

#2


15  

Scott Arciszewski Security notice: The code on this answer is vulnerable to chosen-ciphertext attacks. See this answer instead for secure encryption.

Scott Arciszewski安全注意:这个答案上的代码很容易受到搜索密文攻击。请参阅此答案以获得安全加密。

Here is a working example of encrypting your string with PHP and decrypting it with CryptoJS.

下面是一个使用PHP加密字符串并使用CryptoJS解密字符串的示例。

On the PHP side:

PHP的一面:

Use MCRYPT_RIJNDAEL_128 (not 256) to pair with AES. The 128 here is the blocksize, not the keysize.

使用MCRYPT_RIJNDAEL_128(不是256)对AES进行配对。这里的128是块大小,而不是键大小。

Send the IV, too. You need the IV to decrypt.

发送四世。你需要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";

Here is sample output from a test run:

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

iv:BMcOODpuQurUYGICmOqqbQ==
ciphertext:ZJAab8YtkRq5TL7uyIR7frM2b3krftJzn1pTqRTAda4=

Scott Arciszewski IMPORTANT: Because we are not authenticating our ciphertext, decryption becomes vulnerable to padding oracle attacks. See also: authenticated encryption in PHP.

Scott Arciszewski很重要:因为我们没有对密文进行身份验证,解密变得容易受到攻击。参见:PHP中经过验证的加密。

On the CryptoJS side:

CryptoJS方面:

Your key is only 13 ASCII printable characters which is very weak. Mcrypt padded the key to a valid keysize using ZERO bytes.

你的密钥只有13个ASCII可打印字符,这是非常弱的。Mcrypt使用零字节将密钥填充到一个有效的密钥大小中。

Convert the key and IV to word arrays.

将键和IV转换为单词数组。

I did not have much luck decrypting with ciphertext as a word array, so I've left it in Base64 format.

我用密文作为一个单词数组进行解密时运气不太好,所以我把它保持在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='

/**
 * Scott Arciszewski DANGER DANGER WILL ROBINSON!
 *
 * 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))

#3


3  

You are using two libraries that try to accommodate input that is - strictly speaking - not valid. Rijndael requires keys that 16, 24 or 32 bytes long random byte strings. You provide a 13 character string. Mcrypt, the PHP library, uses the string (presumable utf8 encoded) directly as binary input and zero pads it to the required 32 bytes for MCRYPT_RIJNDAEL_256. CryptoJS on the other hand decides that you have entered something like a passphrase and instead uses a key derivation function to generate a 32 byte key.

您正在使用两个库来尝试适应输入——严格地说——无效的输入。Rijndael需要16、24或32字节的随机字节字符串。您提供了13个字符串。PHP库Mcrypt将字符串(假定的utf8编码)直接作为二进制输入,0将其传递到MCRYPT_RIJNDAEL_256所需的32字节。另一方面,CryptoJS决定您已经输入了诸如密码短语之类的东西,并使用一个密钥派生函数来生成一个32字节的密钥。

Furthermore the encryption algorithms used don't even match. Mcrypt uses a seldom implemented variant of the original Rijndael for the 256 bit version, while CryptoJS implements the widely known variant AES256 of the Rijndael proposal. The 128 bit version of both (MCRYPT_RIJNDAEL_128 and AES128) are identical though.

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

The third problem you are about to face later is that Mcrypt also uses a crazy padding scheme for the data being encrypted. As Rijndael is a block cipher, it can only encrypt blocks of 16, 24 or 32 bytes (depending on the variant - AES always uses 16 byte blocks). As such data has to be padded. Mcrypt does this in a non-injective way by just appending zeros. If you are only encoding strings this will not be so much of a problem for you as utf8 encoded strings never contain zero bytes, so you can just strip them off (CryptoJS even supports that natively).

您稍后将要面对的第三个问题是,Mcrypt还对正在加密的数据使用了一种疯狂的填充方案。由于Rijndael是块密码,它只能加密16、24或32字节的块(取决于变体——AES总是使用16字节块)。这样的数据必须加以补充。Mcrypt以非单射的方式通过添加零来实现这一点。如果您只是对字符串进行编码,那么对于您来说,这将不会成为一个问题,因为utf8编码的字符串从不包含零字节,所以您可以直接将它们去掉(加密甚至支持本机)。

The easiest fix to all these problems is to avoid having to implement any cryptography yourself (it is strongly discouraged anyway without a wide knowledge of the subject). Can you instead transmit your sensitive information over https which will use TLS (formerly called SSL) to encrypt and authenticate the channel?

要解决所有这些问题,最简单的办法就是避免自己实现任何密码(在不了解这个主题的情况下,强烈建议不要这样做)。您是否可以通过https传输敏感信息,而https将使用TLS(以前称为SSL)对通道进行加密和身份验证?

#1


43  

I've required the same thing and i wrote a short library that works for CryptoJS 3.x and PHP with openssl support. Hope this helps, source plus example files here https://github.com/brainfoolong/cryptojs-aes-php

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

PHP Lib

/**
* 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 Lib

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;
    }
}

Example 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));

Example PHP

$encrypted = cryptoJsAesEncrypt("my passphrase", "value to encrypt");
$decrypted = cryptoJsAesDecrypt("my passphrase", $encrypted);

#2


15  

Scott Arciszewski Security notice: The code on this answer is vulnerable to chosen-ciphertext attacks. See this answer instead for secure encryption.

Scott Arciszewski安全注意:这个答案上的代码很容易受到搜索密文攻击。请参阅此答案以获得安全加密。

Here is a working example of encrypting your string with PHP and decrypting it with CryptoJS.

下面是一个使用PHP加密字符串并使用CryptoJS解密字符串的示例。

On the PHP side:

PHP的一面:

Use MCRYPT_RIJNDAEL_128 (not 256) to pair with AES. The 128 here is the blocksize, not the keysize.

使用MCRYPT_RIJNDAEL_128(不是256)对AES进行配对。这里的128是块大小,而不是键大小。

Send the IV, too. You need the IV to decrypt.

发送四世。你需要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";

Here is sample output from a test run:

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

iv:BMcOODpuQurUYGICmOqqbQ==
ciphertext:ZJAab8YtkRq5TL7uyIR7frM2b3krftJzn1pTqRTAda4=

Scott Arciszewski IMPORTANT: Because we are not authenticating our ciphertext, decryption becomes vulnerable to padding oracle attacks. See also: authenticated encryption in PHP.

Scott Arciszewski很重要:因为我们没有对密文进行身份验证,解密变得容易受到攻击。参见:PHP中经过验证的加密。

On the CryptoJS side:

CryptoJS方面:

Your key is only 13 ASCII printable characters which is very weak. Mcrypt padded the key to a valid keysize using ZERO bytes.

你的密钥只有13个ASCII可打印字符,这是非常弱的。Mcrypt使用零字节将密钥填充到一个有效的密钥大小中。

Convert the key and IV to word arrays.

将键和IV转换为单词数组。

I did not have much luck decrypting with ciphertext as a word array, so I've left it in Base64 format.

我用密文作为一个单词数组进行解密时运气不太好,所以我把它保持在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='

/**
 * Scott Arciszewski DANGER DANGER WILL ROBINSON!
 *
 * 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))

#3


3  

You are using two libraries that try to accommodate input that is - strictly speaking - not valid. Rijndael requires keys that 16, 24 or 32 bytes long random byte strings. You provide a 13 character string. Mcrypt, the PHP library, uses the string (presumable utf8 encoded) directly as binary input and zero pads it to the required 32 bytes for MCRYPT_RIJNDAEL_256. CryptoJS on the other hand decides that you have entered something like a passphrase and instead uses a key derivation function to generate a 32 byte key.

您正在使用两个库来尝试适应输入——严格地说——无效的输入。Rijndael需要16、24或32字节的随机字节字符串。您提供了13个字符串。PHP库Mcrypt将字符串(假定的utf8编码)直接作为二进制输入,0将其传递到MCRYPT_RIJNDAEL_256所需的32字节。另一方面,CryptoJS决定您已经输入了诸如密码短语之类的东西,并使用一个密钥派生函数来生成一个32字节的密钥。

Furthermore the encryption algorithms used don't even match. Mcrypt uses a seldom implemented variant of the original Rijndael for the 256 bit version, while CryptoJS implements the widely known variant AES256 of the Rijndael proposal. The 128 bit version of both (MCRYPT_RIJNDAEL_128 and AES128) are identical though.

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

The third problem you are about to face later is that Mcrypt also uses a crazy padding scheme for the data being encrypted. As Rijndael is a block cipher, it can only encrypt blocks of 16, 24 or 32 bytes (depending on the variant - AES always uses 16 byte blocks). As such data has to be padded. Mcrypt does this in a non-injective way by just appending zeros. If you are only encoding strings this will not be so much of a problem for you as utf8 encoded strings never contain zero bytes, so you can just strip them off (CryptoJS even supports that natively).

您稍后将要面对的第三个问题是,Mcrypt还对正在加密的数据使用了一种疯狂的填充方案。由于Rijndael是块密码,它只能加密16、24或32字节的块(取决于变体——AES总是使用16字节块)。这样的数据必须加以补充。Mcrypt以非单射的方式通过添加零来实现这一点。如果您只是对字符串进行编码,那么对于您来说,这将不会成为一个问题,因为utf8编码的字符串从不包含零字节,所以您可以直接将它们去掉(加密甚至支持本机)。

The easiest fix to all these problems is to avoid having to implement any cryptography yourself (it is strongly discouraged anyway without a wide knowledge of the subject). Can you instead transmit your sensitive information over https which will use TLS (formerly called SSL) to encrypt and authenticate the channel?

要解决所有这些问题,最简单的办法就是避免自己实现任何密码(在不了解这个主题的情况下,强烈建议不要这样做)。您是否可以通过https传输敏感信息,而https将使用TLS(以前称为SSL)对通道进行加密和身份验证?