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);
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.0.0/crypto-js.min.js"></script>
【讨论】:
虽然此代码可能会回答问题,但提供有关它如何和/或为什么解决问题的额外上下文将提高答案的长期价值。以上是关于JavaScript 加密和 PHP 解密的主要内容,如果未能解决你的问题,请参考以下文章
php中的AES加密,然后用Javascript(cryptojs)解密
在 php 中加密,然后在 javascript 中使用相同的密钥解密