JavaScript 加密和 PHP 解密

Posted

技术标签:

【中文标题】JavaScript 加密和 PHP 解密【英文标题】:Encryption in JavaScript and decryption with PHP 【发布时间】:2015-02-24 22:51:51 【问题描述】:

我正在像这样在 javascript 中加密我的用户密码:

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

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

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

根本不行,解密后的密码串看起来很奇怪:

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

在有用的 cmets 之后,这是我在 JavaScript 中的代码的当前状态:

    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

我正在将 saltHex 和 CipherTextHex 发送到 PHP 服务器,并且我正在使用 mcrypt_decrypt(),如下所示:

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

更新后的代码仍然无法使用。

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

【问题讨论】:

@ArtjomB。能否请您提供更精确的信息? JavaScript加密和在PHP解密有什么好处,因为你可以在PHP进行加密和解密 @lawrenceoverflow 使用 JavaScript 在客户端加密用于不以明文形式将登录表单发布到我的 PHP 服务器。 看这里:***.com/a/27250883/1816580 如果需要,可以将代码转换为 php 如果您没有 IV 和盐,那么您就不走运了。您没有显示encryptedPassword 是如何传输的,因为它可能被编码到它。 CryptoJS 使用 OpenSSLFormatter。您应该查看 CryptoJS 的代码。 【参考方案1】:

您无法使用随机初始化向量进行解密 - 您需要使用与数据加密相同的 IV。此外,IIRC、AES 默认为加密数据的 8 位表示,在通过 HTTP 传输时需要小心处理。

【讨论】:

有一件事,我遇到了 md5 的问题。当我通过清除密码来检查用户的登录时,md5 哈希等于 mysql 数据库中的 md5 哈希。但是当我使用解密后的密码时,md5 是不同的。这很奇怪,因为明文密码字符串与解密字符串相同,而 md5 哈希值不同。【参考方案2】:

问题在于,在 CryptoJS 代码中,密码用于派生密钥和用于 AES 加密的 IV,但 mcrypt 仅使用密钥来加密/解密。需要将此信息传递给 php.ini。既然你不想传递密码,你必须在php中以同样的方式推导出密钥和IV。

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

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 之前,盐必须从十六进制转换为二进制字符串。

$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 编码的。

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:

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;

【讨论】:

在我对Java 和Python 的回答中可以找到类似的代码。 安全声明:提供此代码是为了实现不同编程语言之间的兼容性。它不一定是完全安全的。它的安全性取决于密码的复杂性和长度,因为只有一次迭代和使用 MD5。我建议使用至少 20 个字符的密码,最好是随机生成的字母数字字符。 非常感谢@Artjom B.!我希望我能给你+100。这很有帮助。【参考方案3】:
/** 
 *-------------PHP code example-----------------
 */
/**
 * 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);

$encrypted = '"ct":"nPfd1U0y9o2hRCdwJK6XkM1E01wa1ZjMu3eAzGjUD60=","iv":"2abda27fc571cf74e6efc1ba564801f9","s":"813a340e805f54ae"';
$key = "123456";
$decrypted = cryptoJsAesDecrypt($key, $encrypted);

/* -------------Javascript code example-----------------*/
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;
    

var key = "123456";
var encrypted = CryptoJS.AES.encrypt(JSON.stringify("value to encrypt"), key, format: CryptoJSAesJson).toString();
console.log(encrypted);
var decrypted = JSON.parse(CryptoJS.AES.decrypt(encrypted, key, format: CryptoJSAesJson).toString(CryptoJS.enc.Utf8));
console.log("decryyepted: "+decrypted);
&lt;script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.0.0/crypto-js.min.js"&gt;&lt;/script&gt;

【讨论】:

虽然此代码可能会回答问题,但提供有关它如何和/或为什么解决问题的额外上下文将提高​​答案的长期价值。

以上是关于JavaScript 加密和 PHP 解密的主要内容,如果未能解决你的问题,请参考以下文章

用 PHP 加密消息,用 JavaScript 解密

php中的AES加密,然后用Javascript(cryptojs)解密

在 php 中加密,然后在 javascript 中使用相同的密钥解密

用 Javascript 加密,用 PHP 解密,使用公钥加密

java加密用PHP解密

SSL 替代方案 - 使用 JavaScript 加密密码提交给 PHP 进行解密