首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >用PHP (openssl_encrypt)加密,然后用JS (CryptoJS)解密

用PHP (openssl_encrypt)加密,然后用JS (CryptoJS)解密
EN

Stack Overflow用户
提问于 2015-09-10 16:25:04
回答 2查看 5K关注 0票数 5

我第一次使用openssl_encrypt(),我很难解密我在PHP中使用CryptoJS加密的字符串。

PHP5.6.13.0和CryptoJS 3.1.2

首先,我的PHP:

代码语言:javascript
运行
复制
$encryptHash = hash_pbkdf2("sha256", "0000", "secret", 1000, 32);
var_dump($encryptHash);

$iv = openssl_random_pseudo_bytes(openssl_cipher_iv_length('aes-256-cbc'));
var_dump(bin2hex($iv));

$encrypted = openssl_encrypt("hello! this is my string!", 'aes-256-cbc', $encryptHash, 0, $iv);
var_dump($encrypted);

$encrypted = base64_encode($encrypted.":".bin2hex($iv));
echo "\r\n".$encrypted;

这给了我以下输出:

代码语言:javascript
运行
复制
string(32) "59b6ab46d379b89d794c87b74a511fbd"
string(32) "0aaff094b6dc29742cc98a4bac8bc8f9"
string(44) "xHIxg1HDUOqyhBmAaU2Sx3ct8GaKaeE5w4d1KM1yuDw="

eEhJeGcxSERVT3F5aEJtQWFVMlN4M2N0OEdhS2FlRTV3NGQxS00xeXVEdz06MGFhZmYwOTRiNmRjMjk3NDJjYzk4YTRiYWM4YmM4Zjk=

现在我的JS:

代码语言:javascript
运行
复制
var encryptedString = "eEhJeGcxSERVT3F5aEJtQWFVMlN4M2N0OEdhS2FlRTV3NGQxS00xeXVEdz06MGFhZmYwOTRiNmRjMjk3NDJjYzk4YTRiYWM4YmM4Zjk=";

var key256Bits  = CryptoJS.PBKDF2("0000", "secret", { keySize: 128/32, iterations: 1000, hasher: CryptoJS.algo.SHA256 });
var keyAsHex = key256Bits.toString(CryptoJS.enc.Hex);

/* keyAsHex = "59b6ab46d379b89d794c87b74a511fbd" */

var rawData = atob(encryptedString);
var rawPieces = rawData.split(":");

var crypttext = rawPieces[0];
var iv = rawPieces[1];

/* crypttext = "xHIxg1HDUOqyhBmAaU2Sx3ct8GaKaeE5w4d1KM1yuDw=" */
/* iv = "0aaff094b6dc29742cc98a4bac8bc8f9" */

/* So far so good? */

var plaintextArray = CryptoJS.AES.decrypt(
  { ciphertext: CryptoJS.enc.Base64.parse(crypttext) },
  CryptoJS.enc.Hex.parse(keyAsHex),
  { iv: CryptoJS.enc.Hex.parse(iv) }
);

/* plaintextArray: d.WordArray.n.extend.init
    sigBytes: -67
    words: Array[8]
        0: 1419734786
        1: -2048883413
        2: -1709437124
        3: 736946566
        4: 718053567
        5: -64039355
        6: 1868905697
        7: -910423965 */

var output = CryptoJS.enc.Utf8.stringify(plaintextArray);

/* output = "" */

如您所见,我的输出是一个空字符串。有人想做类似的事吗?我被困住了!

编辑

原来我的钥匙长度不对!下面是我的工作PHP (加密)和JS (解密)代码:

PHP:

代码语言:javascript
运行
复制
$encryptHash = hash_pbkdf2("sha256", "0000", "secret", 1000, 32, true);
var_dump($encryptHash);

$iv = openssl_random_pseudo_bytes(openssl_cipher_iv_length("aes-256-cbc"));
var_dump($iv);

$encrypted = openssl_encrypt("hello! this is a test!", "aes-256-cbc", $encryptHash, 0, $iv);
var_dump($encrypted);

$encrypted = base64_encode($encrypted.":".bin2hex($iv));
echo "\r\n".$encrypted;

给我以下内容:

代码语言:javascript
运行
复制
string(32) "Y½FËy©ØyLçÀJQ▼¢▄▄êI╩öo§(NtÙת‼ç"
string(16) "àX§ $VÇ‼♣┘█²áÓßt"
string(44) "VIzzao8Wdo8HPM015v6c5Q77ervGUIVbL6ERKRXb0fU="

Vkl6emFvOFdkbzhIUE0wMTV2NmM1UTc3ZXJ2R1VJVmJMNkVSS1JYYjBmVT06ODU1ODE1MjAyNDU2ODAxMzA1ZDlkYmZkYTBlMGUxNzQ=

联署材料:

代码语言:javascript
运行
复制
var encryptedString = "Vkl6emFvOFdkbzhIUE0wMTV2NmM1UTc3ZXJ2R1VJVmJMNkVSS1JYYjBmVT06ODU1ODE1MjAyNDU2ODAxMzA1ZDlkYmZkYTBlMGUxNzQ=";

var key256Bits  = CryptoJS.PBKDF2("0000", "secret", { keySize: 256/32, iterations: 1000, hasher: CryptoJS.algo.SHA256 });

var rawData = atob(encryptedString);
var rawPieces = rawData.split(":");

var crypttext = rawPieces[0];
var iv = CryptoJS.enc.Hex.parse(rawPieces[1]);

var cipherParams = CryptoJS.lib.CipherParams.create({ciphertext: CryptoJS.enc.Base64.parse(crypttext)});

var plaintextArray = CryptoJS.AES.decrypt(
  cipherParams,
  key256Bits,
  { iv: iv }
);

var output = CryptoJS.enc.Utf8.stringify(plaintextArray);

/* output === 'hello! this is a test!' */
EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2015-09-11 06:58:30

TL;DR -尝试使用32字节密钥而不是16字节密钥.

在撰写了较早前的答案,并最终将其删除后,我推翻了我自己关于这个问题是填充问题的理论:-)现在我相当肯定,问题可能只是与密钥长度有关。

在尝试复制您的问题时,我无法获得第一个密文块在使用openssl_encryptCryptoJS生成时相同。然后我把钥匙的长度翻了一倍,它起作用了。

上面生成的键是32个字符,但一旦转换后只有16个字节,所以尝试加倍,看看会发生什么。

FWIW,下面是我用来测试密钥长度的PHP代码:

代码语言:javascript
运行
复制
$data = "hello! this is a test!";
$method = 'aes-256-cbc';
$key = '59b6ab46d379b89d794c87b74a511fbd59b6ab46d379b89d794c87b74a511fbd';
$iv = '0aaff094b6dc29742cc98a4bac8bc8f9';

$e = openssl_encrypt( $data, $method, hex2bin( $key ), 0, hex2bin( $iv ));

echo 'Ciphertext: [', bin2hex( base64_decode( $e )), "]\n";
echo 'Key:        [', $key, "]\n";
echo 'Cleartext:  [', openssl_decrypt( $e, $method, hex2bin( $key ), 0, hex2bin( $iv )), "]\n";

// Test with openssl on the command line as well, just to be sure!
file_put_contents( 'clear.txt', $data );

$exec = "openssl enc -$method -e -in clear.txt -out encrypted.txt -base64 -nosalt -K $key -iv $iv";
exec ($exec);
$out = file_get_contents( 'encrypted.txt' );
echo 'Ciphertext: [', bin2hex( base64_decode(trim($out))), "]\n";

下面是兼容的JavaScript,我在Mac上使用jsc运行它:

代码语言:javascript
运行
复制
var data = "hello! this is a test!";
var key = '59b6ab46d379b89d794c87b74a511fbd59b6ab46d379b89d794c87b74a511fbd';
var iv = '0aaff094b6dc29742cc98a4bac8bc8f9';

var encrypted = CryptoJS.AES.encrypt(CryptoJS.enc.Utf8.parse(data), CryptoJS.enc.Hex.parse(key), { iv: CryptoJS.enc.Hex.parse(iv) });

print( 'Ciphertext: [' + encrypted.ciphertext + ']' );
print( 'Key:        [' + encrypted.key + ']' );

cipherParams = CryptoJS.lib.CipherParams.create({ciphertext: CryptoJS.enc.Hex.parse(encrypted.ciphertext.toString())});
var decrypted = CryptoJS.AES.decrypt(cipherParams, CryptoJS.enc.Hex.parse(key), { iv: CryptoJS.enc.Hex.parse(iv) });

print( 'Cleartext:  [' + decrypted.toString(CryptoJS.enc.Utf8) + ']');

这两段代码产生相同的密文,而不管输入的长度如何,这证实了填充策略在两个库之间是兼容的。然而,如果将密钥的长度减半,密文将不再相同,这显然意味着解密也是不兼容的。

更新

我刚刚发现hash_pbkdf2()默认返回ASCII十六进制字符串,所以在传递给openssl_encrypt()之前,您应该将$encryptHash转换为带有hex2bin()的二进制,或者将hash_pbkdf2()的最后一个参数设置为true以获得原始输出。

更新2

我刚刚确认,如果您做了以下更改,您的代码就会正常工作:

在PHP中,将键大小从32字节更改为64字节,并在生成键时添加原始输出选项:

代码语言:javascript
运行
复制
$encryptHash = hash_pbkdf2("sha256", "0000", "secret", 1000, 64, 1);

将JavaScript中的密钥长度从128位更改为256位:

代码语言:javascript
运行
复制
var key256Bits  = CryptoJS.PBKDF2("0000", "secret", { keySize: 256/32, iterations: 1000, hasher: CryptoJS.algo.SHA256 });

希望当你尝试这些改变时,这些改变都能奏效。

票数 4
EN

Stack Overflow用户

发布于 2021-01-27 17:07:44

关于错误密钥长度和bool $binary = true in hash_pbkdf2的想法也解决了我的所有问题,因为一旦开始深入研究问题,就不会那么明显了。我正在添加我的解决方案与一些额外的解释,这样一个人可以节省一些时间,同时寻找所有这些信息。

我发现的另一个非常重要的细节是0函数的加密选项参数,这给在PHP中加密和在JS中解密带来了更多的混乱。这对返回的数据格式有很大的影响,我们应该知道使用CryptoJS。

将第四个选项的参数设置为0后,返回的数据由openssl_encrypt编码为Base64,因此需要从CryptoJS中的Base64中解码两次。但是,如果将该选项设置为OPENSSL_RAW_DATA,则数据不会在PHP中隐式编码到Base64。

PHP 7.4:

代码语言:javascript
运行
复制
    $plainTextToEncrypt = "Lorem ipsum";
    $passphrase = "obligate properly elective edge"; // from: https://www.useapassphrase.com
    $iterations = 1001;
    $salt = random_bytes(32);
    // It doesn't really matter how many characters the `salt` is.
    $hexSalt = bin2hex($salt);
    var_dump($hexSalt); 
    // string (64): 0c5597db78ac4aedf2bdfb1d4ce7935c270876284239b0ef48ba63d08ed164b5
    
    $key = hash_pbkdf2("sha256", $passphrase, $hexSalt, $iterations, 32, true);
    var_dump($key);
    // in raw as string (32) it then looks like this: �g��)���2�'�����M2eCY�I�J��^

    // More readable 64 lowercase hexits long key:
    $keyToHex = bin2hex($key);
    var_dump($keyToHex);
    // string (64): ce670c8a7f8329b78306d9329927faa4f880c34d32654359e949a74aa77fc45e

    /*
       By the way, $keyToHex is the same as the following 32 characters long
       raw binary $key if generated as 64 lowercase hexits long key (as it's discussed
       in the previous post in the 'UPDATE' section):
    */
    $keyIn64Hexits = hash_pbkdf2("sha256", $passphrase, $hexSalt, $iterations, 64, false);
    var_dump($keyIn64Hexits);
    // string (64):  ce670c8a7f8329b78306d9329927faa4f880c34d32654359e949a74aa77fc45e

    /*
        DON'T confuse yourself here! Below I encrypt with the raw binary $key, which is
        32 characters long, and with the `OPENSSL_RAW_DATA` option in `openssl_encrypt()`.
    */

    $cipher = 'aes-256-ctr';
    if (in_array($cipher, openssl_get_cipher_methods()))
    {
        $ivLen = openssl_cipher_iv_length($cipher);
        var_dump($ivLen);
        // int (16) <--- should be of the appropriate length used in the encryption algorithm of your choice!
        $iv = random_bytes($ivLen);
        var_dump($iv);
        // string (16): 7��������a��
        $ivInHex = bin2hex($iv); // <--- In CryptoJS I'm working with the Hex variant once it's decoded from Base64 (see the 2nd code block with my JS)
        var_dump($ivInHex);
        // string (32): 3706f4f089c2f6f2e0aafa6191170dae
        $iv64 = base64_encode($ivInHex);
        // $iv64 in Base64 looks like this: MzcwNmY0ZjA4OWMyZjZmMmUwYWFmYTYxOTExNzBkYWU=

        $encryptedData = openssl_encrypt($plainTextToEncrypt, $cipher, $key, OPENSSL_RAW_DATA, $ivInHex);
        // with `OPENSSL_RAW_DATA` you'll get the raw binary data: ùg3UDCY��
        // bin2hex($encryptedData) looks like: c3b967335544430759b2c1
        // base64_encode($encryptedData) looks like: w7lnM1VEQwdZssE= <--- btw, this is how one-time encoding to Base64 looks like

        // Let's prepare it for transport
        $data = array("ciphertext" => base64_encode($encryptedData), "iv" => $iv64, "salt" => $hexSalt);
        // Whatever you're doing with the encrypted data later, e.g.:
        // return json_encode($data);
    }

CryptoJS 4.0.0

代码语言:javascript
运行
复制
    const encryptedPlainText = "w7lnM1VEQwdZssE=";
    const passphrase = "obligate properly elective edge"; // don't save it here, get it from some other place
    const salt = "0c5597db78ac4aedf2bdfb1d4ce7935c270876284239b0ef48ba63d08ed164b5"; // 64 characters
    const iv = "MzcwNmY0ZjA4OWMyZjZmMmUwYWFmYTYxOTExNzBkYWU=";
    const parsedSalt = CryptoJS.enc.Hex.parse(salt); // or: CryptoJS.enc.Latin1.parse(salt);
    const parsedIV = CryptoJS.enc.Base64.parse(iv);

    const key = CryptoJS.PBKDF2(passphrase, parsedSalt, {
        hasher: CryptoJS.algo.SHA256,
        keySize: 256 / 32, // the length of the key is then 32 characters
        iterations: 1001,
    });
    // you can check the length in bytes like so:
    console.log("KEY (in bytes in Latin1):",
        CryptoJS.enc.Latin1.parse(CryptoJS.enc.Latin1.stringify(key))
      );
    // KEY (in bytes in Latin1):  t.init {words: Array(8), sigBytes: 32}

    console.log("KEY (toString in Latin1): ", key.toString(CryptoJS.enc.Latin1));
    // KEY (toString in Latin1):  ÎgŠƒ)·ƒÙ2™'ú¤ø€ÃM2eCYéI§J§Ä^

    // However in UTF-8 is's 64 characters, so keep this in mind:
    console.log("KEY (Utf8): ", key.toString());
    // ce670c8a7f8329b78306d9329927faa4f880c34d32654359e949a74aa77fc45e
    
    const decrypted = CryptoJS.AES.decrypt(
        {
          ciphertext: CryptoJS.enc.Base64.parse(encryptedPlainText),
        },
        key,
        {
          keySize: 32, // optional here, as it was set in CryptoJS.PBKDF2() above
          iv: parsedIV,
          mode: CryptoJS.mode.CTR,
          padding: CryptoJS.pad.NoPadding,
          /*
              Depending on the contents of the data you're encrypting (trailing spaces or alike),
              the padding can also be set to 'NoPadding' to avoid the additional
              characters or blocks of padding.

              See this post for explanation:
              https://stackoverflow.com/questions/48673427/cryptojs-with-hex-key-not-decrypting-properly
              I use `NoPadding`, since encryptedPlainText is already encoded into Base64.
              Look this post for more details on this topic:
              https://stackoverflow.com/questions/61717485/incorrect-decrypted-string-implemented-using-aes-ecb-nopadding-and-base-64-with/61737626
          */
        }
      );

    console.log("DECRYPTED TEXT:", decrypted);
    // DECRYPTED TEXT: t.init {words: Array(4), sigBytes: 11}
    // It corresponds to 1 byte per character as in `Latin1` encoding.
    // The `Lorem ipsum` text decrypted below is 11 bytes long in Latin1.
    // See: https://stackoverflow.com/questions/2708958/differences-between-utf8-and-latin1

    console.log("DECRYPTED (in UTF8):", CryptoJS.enc.Utf8.parse(decrypted));
    // DECRYPTED (UTF8): t.init {words: Array(6), sigBytes: 22}
    // It corresponds to the UTF8's 2 bytes per character.

    console.log("DECRYPTED (toString in Latin1):", decrypted.toString(CryptoJS.enc.Latin1));
    // DECRYPTED (toString in Latin1): Lorem ipsum
票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/32507094

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档