JavaScript 加密和 PHP 解密

2021-12-20 00:00:00 encryption php javascript mcrypt cryptojs

我像这样用 JavaScript 加密我的用户密码:

I'm encrypting my user password in JavaScript like this:

 var encryptedPassword = CryptoJS.AES.encrypt(password, "Secret Passphrase");

它工作正常,但现在我正在尝试在服务器端用 PHP 解密,如下所示:

It works fine but now I'm trying to decrypt in PHP on the server side like this:

 $iv = mcrypt_create_iv(mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC), MCRYPT_RAND);
 $decryptPassword = mcrypt_decrypt(MCRYPT_RIJNDAEL_128, "Secret Passphrase", base64_decode($password), MCRYPT_MODE_CBC, $iv);

根本不起作用,解密后的密码字符串看起来很奇怪:

it doesn't works at all, the decrypted password string looks very strange:

 string(64) ">�OX2MS��댗v�<$�ʕ��i�̄��_��P����կ=�_6(�m����,4WT7��a"

以下是在有用的注释之后我的 JavaScript 代码的当前状态:

Here is the current state of my code in JavaScript after the helpful comments:

    var encryptedPassword = CryptoJS.AES.encrypt(password, "Secret Passphrase");
    var ivHex = encryptedPassword.iv.toString();
    var ivSize = encryptedPassword.algorithm.ivSize; // same as blockSize
    var keySize = encryptedPassword.algorithm.keySize;
    var keyHex = encryptedPassword.key.toString();
    var saltHex = encryptedPassword.salt.toString(); // must be sent
    var openSslFormattedCipherTextString = encryptedPassword.toString(); // not used
    var cipherTextHex = encryptedPassword.ciphertext.toString(); // must be sent

我正在向 PHP 服务器发送 saltHex 和 CipherTextHex,我正在使用 mcrypt_decrypt() 如下:

I am sending saltHex and CipherTextHex to the PHP server and I'm using mcrypt_decrypt() like this:

 $iv = mcrypt_create_iv(mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC), $saltHex);
 $decryptPassword = mcrypt_decrypt(MCRYPT_RIJNDAEL_128, "Secret Passphrase", base64_decode($cipherTextHex), MCRYPT_MODE_CBC, $iv);

它仍然无法使用此更新的代码.

It still does't work with this updated code.

有人可以帮助我使用 mcrypt_decrypt() PHP 函数正确解密一个简单的 AES 加密方法吗?我确定我的 mcrypt_decrypt() 方法中的密码、mcrypt 模式和 IV 参数有问题.知道的话谢谢.

Can someone help me to decrypt properly with mcrypt_decrypt() PHP function for a simple AES encryption method ? I'm sure I am doing something wrong with the cipher, mcrypt mode and the IV parameters inside my mcrypt_decrypt() method. Thanks if you know.

推荐答案

问题是在 CryptoJS 代码中,密码用于派生密钥和用于 AES 加密的 IV,但 mcrypt 仅使用密钥来加密/解密.这个信息需要传递给php.既然不想传密码,就得用php中同样的方法导出key和IV.

The problem is that in the CryptoJS code a password is used to derive the key and the IV to be used for AES encryption, but mcrypt only uses the key to encrypt/decrypt. This information needs to be passed to php. Since you don't want to transmit the password, you have to derive the key and IV in the same way in php.

以下代码从密码和盐派生密钥和 IV.它是根据我的回答此处中的代码建模的(了解更多信息).

The following code derives the key and IV from a password and salt. It is modeled after the code in my answer here (for more information).

function evpKDF($password, $salt, $keySize = 8, $ivSize = 4, $iterations = 1, $hashAlgorithm = "md5") {
    $targetKeySize = $keySize + $ivSize;
    $derivedBytes = "";
    $numberOfDerivedWords = 0;
    $block = NULL;
    $hasher = hash_init($hashAlgorithm);
    while ($numberOfDerivedWords < $targetKeySize) {
        if ($block != NULL) {
            hash_update($hasher, $block);
        }
        hash_update($hasher, $password);
        hash_update($hasher, $salt);
        $block = hash_final($hasher, TRUE);
        $hasher = hash_init($hashAlgorithm);

        // Iterations
        for ($i = 1; $i < $iterations; $i++) {
            hash_update($hasher, $block);
            $block = hash_final($hasher, TRUE);
            $hasher = hash_init($hashAlgorithm);
        }

        $derivedBytes .= substr($block, 0, min(strlen($block), ($targetKeySize - $numberOfDerivedWords) * 4));

        $numberOfDerivedWords += strlen($block)/4;
    }

    return array(
        "key" => substr($derivedBytes, 0, $keySize * 4),
        "iv"  => substr($derivedBytes, $keySize * 4, $ivSize * 4)
    );
}

salt是CryptoJS加密时生成的,需要和密文一起发送给php.在调用 evpKDF 之前,盐必须从十六进制转换为二进制字符串.

The salt is generated during encryption in CryptoJS and needs to be sent to php with the ciphertext. Before invoking evpKDF the salt has to be converted to a binary string from hex.

$keyAndIV = evpKDF("Secret Passphrase", hex2bin($saltHex));
$decryptPassword = mcrypt_decrypt(MCRYPT_RIJNDAEL_128, 
        $keyAndIV["key"], 
        hex2bin($cipherTextHex), 
        MCRYPT_MODE_CBC, 
        $keyAndIV["iv"]);

<小时>

如果只有encryptedPassword.toString() 被发送到服务器,那么在使用之前需要将salt和实际密文分开.该格式是专有的 OpenSSL 兼容格式,前 8 个字节是Salted__",接下来的 8 个字节是随机盐,其余的是实际密文.所有的东西都是 Base64 编码的.


If only encryptedPassword.toString() was sent to the server, then it is necessary to split the salt and actual ciphertext before use. The format is a proprietary OpenSSL-compatible format with the first 8 bytes being "Salted__", the next 8 bytes being the random salt and the rest is the actual ciphertext. Everything together is Base64-encoded.

function decrypt($ciphertext, $password) {
    $ciphertext = base64_decode($ciphertext);
    if (substr($ciphertext, 0, 8) != "Salted__") {
        return false;
    }
    $salt = substr($ciphertext, 8, 8);
    $keyAndIV = evpKDF($password, $salt);
    $decryptPassword = mcrypt_decrypt(MCRYPT_RIJNDAEL_128, 
            $keyAndIV["key"], 
            substr($ciphertext, 16), 
            MCRYPT_MODE_CBC, 
            $keyAndIV["iv"]);

    // unpad (PKCS#7)
    return substr($decryptPassword, 0, strlen($decryptPassword) - ord($decryptPassword[strlen($decryptPassword)-1]));
}

使用 OpenSSL 扩展代替 Mcrypt 可以实现相同的目的:

The same can be achieved with the OpenSSL extension instead of Mcrypt:

function decrypt($ciphertext, $password) {
    $ciphertext = base64_decode($ciphertext);
    if (substr($ciphertext, 0, 8) != "Salted__") {
        return false;
    }
    $salt = substr($ciphertext, 8, 8);
    $keyAndIV = evpKDF($password, $salt);
    $decryptPassword = openssl_decrypt(
            substr($ciphertext, 16), 
            "aes-256-cbc",
            $keyAndIV["key"], 
            OPENSSL_RAW_DATA, // base64 was already decoded
            $keyAndIV["iv"]);

    return $decryptPassword;
}

相关文章