我正在尝试用 PHP 和 JS 加密和解密字符串。
PHP
function encrypt_decrypt($action, $string)
{
$output = false;
$encrypt_method = "AES-256-CBC";
$secret_key = '$6Za+?k}^`5q:f^@TSxy69gf7JcKuF!,';
$secret_iv = '$6Za+?k}^`5q:f^@TSxy69gf7JcKuF!,';
$key = hash('sha256', $secret_key);
// echo $key.' '; // bc11b452cdf3f8155b5fd3e3711b1d0712638c613f33f5551b96d5bb37b490ae
// echo strlen($key); // 64
// iv - encrypt method AES-256-CBC expects 16 bytes - else you will get a warning
$iv = substr(hash('sha256', $secret_iv), 0, 16);
// echo $iv.' '; // bc11b452cdf3f815
if ($action == 'encrypt') {
$output = openssl_encrypt($string, $encrypt_method, $key, 0, $iv);
$output = base64_encode($output); // output is base 64 encoded
} else if ($action == 'decrypt') {
$output = openssl_decrypt(base64_decode($string), $encrypt_method, $key, 0, $iv);
}
return $output;
}
$encrypted = encrypt_decrypt('encrypt', 'alaks@123');
// echo "encrypted string is " . $encrypted; // Rko0Q0lsdnl3Ukp6RkI2bXA4STF6QT09
// echo "decrypted string is " . encrypt_decrypt('decrypt', $encrypted); // alaks@123
我正在尝试应用相同的 secret_key 和 secret_iv 并获得密钥和 iv 匹配
JS
<script src="node_modules/crypto-js/crypto-js.js"></script>
<script>
const secret_key = '$6Za+?k}^`5q:f^@TSxy69gf7JcKuF!,';
const secret_iv = '$6Za+?k}^`5q:f^@TSxy69gf7JcKuF!,';
const text = 'alaks@123';
const key = CryptoJS.SHA256(secret_key).toString();
const iv = CryptoJS.SHA256(secret_iv).toString().substring(0, 16);
// console.log("key", key); // bc11b452cdf3f8155b5fd3e3711b1d0712638c613f33f5551b96d5bb37b490ae
// console.log("iv", iv); // bc11b452cdf3f815
function aesEncrypt(data) {
let cipher = CryptoJS.AES.encrypt(data, key, {
iv: iv,
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.Pkcs7
});
return btoa(cipher.toString()); // convert the encrypted string to base64
}
function aesDecrypt(data) {
let cipher = CryptoJS.AES.decrypt(atob(data), key, {
iv: iv,
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.Pkcs7
});
return cipher.toString(CryptoJS.enc.Utf8);
}
const encryptedText = aesEncrypt(text);
const decryptedText = aesDecrypt(encryptedText);
console.log('Encrypted Text - ' + encryptedText); // this encrypted text is different from php generated
console.log('Decrypted Text - ' + decryptedText);
// actual encrypted text = VTJGc2RHVmtYMSsvblljaUpsdElzQ2w4eVFsRnZHS01oR2g2WS8raTJLbz0=
// expected encrypted text = Rko0Q0lsdnl3Ukp6RkI2bXA4STF6QT09
</script>
生成的密文也和PHP不一致。以下是每次刷新时的加密文本
encrypted text generated = VTJGc2RHVmtYMTlISHlLdlBGdDB5Sitlb3pnY04wQjJtWWIwV0RCR1I1Yz0=
encrypted text generated = VTJGc2RHVmtYMS9XZXNyTzhMWkZqdEtpVkF1MURJeUdzUlhqUUlUcXRlTT0=
encrypted text generated = VTJGc2RHVmtYMTh3dHY0VDBBanhzbnltejV1K1MxV2FYb1ZXalJuTjJxZz0=
encrypted text generated = VTJGc2RHVmtYMTh3Z081YVViNnc3dCtZUlAvN05NZ09nRUh0blB0eTBpaz0=
我的问题是,CrptoJS 设置应该是什么,以获得与 PHP 中相同的编码输出 (Rko0Q0lsdnl3Ukp6RkI2bXA4STF6QT09)
const secret_key = '$6Za+?k}^`5q:f^@TSxy69gf7JcKuF!,';
const secret_iv = '$6Za+?k}^`5q:f^@TSxy69gf7JcKuF!,';
const text = 'alaks@123';
const key = CryptoJS.SHA256(secret_key).toString().substring(0,32);
const iv = CryptoJS.SHA256(secret_iv).toString().substring(0, 16);
console.log("key", key); // bc11b452cdf3f8155b5fd3e3711b1d0712638c613f33f5551b96d5bb37b490ae
// console.log("iv", iv); // bc11b452cdf3f815
function aesEncrypt(data) {
let cipher = CryptoJS.AES.encrypt(data, key, {
iv: iv,
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.Pkcs7
});
return btoa(cipher.toString()); // convert the encrypted string to base64
}
function aesDecrypt(data) {
let cipher = CryptoJS.AES.decrypt(atob(data), key, {
iv: iv,
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.Pkcs7
});
return cipher.toString(CryptoJS.enc.Utf8);
}
//**Edit 1: Added Code Snippet**
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.1.1/crypto-js.min.js"></script>
结合 PHP 和 JS 代码
<?php
$text = 'alaks@123';
define('SECRET_KEY', '$6Za+?k}^`5q:f^@TSxy69gf7JcKuF!,');
define('SECRET_IV', '$6Za+?k}^`5q:f^@TSxy69gf7JcKuF!,');
function encrypt_decrypt($action, $string)
{
$collectionInPhp = [];
$encrypt_method = "AES-256-CBC";
$output = false;
$key = hash('sha256', SECRET_KEY);
$iv = substr(hash('sha256', SECRET_IV), 0, 16);
$collectionInPhp['secret_key'] = SECRET_KEY;
$collectionInPhp['secret_iv'] = SECRET_IV;
$collectionInPhp['key'] = $key;
$collectionInPhp['iv'] = $iv;
if ($action == 'encrypt') {
$output = openssl_encrypt($string, $encrypt_method, $key, 0, $iv);
$collectionInPhp['before_base64_encode'] = $output;
$output = base64_encode($output);
$collectionInPhp['after_base64_encode'] = $output;
} else if ($action == 'decrypt') {
$output = openssl_decrypt(base64_decode($string), $encrypt_method, $key, 0, $iv);
}
return [$output, $collectionInPhp];
}
$result = encrypt_decrypt('encrypt', $text);
$encrypted = $result[0];
$collectionInPhp = $result[1];
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/pure/3.0.0/pure-min.css"
integrity="sha512-X2yGIVwg8zeG8N4xfsidr9MqIfIE8Yz1It+w2rhUJMqxUwvbVqC5OPcyRlPKYOw/bsdJut91//NO9rSbQZIPRQ=="
crossorigin="anonymous" referrerpolicy="no-referrer" />
</head>
<body>
<table class="pure-table pure-table-bordered">
<thead>
<tr>
<th>#</th>
<th>PHP</th>
<th>JS</th>
</tr>
</thead>
<tbody>
<tr>
<td>Secret Key</td>
<td class="php secret-key"></td>
<td class="js secret-key"></td>
</tr>
<tr>
<td>Secret IV</td>
<td class="php secret-iv"></td>
<td class="js secret-iv"></td>
</tr>
<tr>
<td>Key</td>
<td class="php key"></td>
<td class="js key"></td>
</tr>
<tr>
<td>IV</td>
<td class="php iv"></td>
<td class="js iv"></td>
</tr>
<tr>
<td>Before Base64Encode</td>
<td class="php beforeBase64Encode"></td>
<td class="js"></td>
</tr>
<tr>
<td>Encrypted</td>
<td class="php encrypted"></td>
<td class="js"></td>
</tr>
<tr>
<td>Received in JS</td>
<td class="php"></td>
<td class="js encrypted"></td>
</tr>
<tr>
<td>After Base64Decode</td>
<td class="php"></td>
<td class="js afterBase64Decode"></td>
</tr>
<tr>
<td>Decrypted</td>
<td class="php"></td>
<td class="js decrypted"></td>
</tr>
</tbody>
</table>
<script src="node_modules/crypto-js/crypto-js.js"></script>
<script>
const collectionInPhp = <?php echo json_encode($collectionInPhp); ?>;
const encrypted = "<?php echo $encrypted; ?>";
const base64Decoded = atob("<?php echo $encrypted; ?>");
// const secret_key = CryptoJS.enc.Utf8.parse("<?php // echo SECRET_KEY; ?>");
// const secret_iv = CryptoJS.enc.Utf8.parse("<?php // echo SECRET_IV; ?>");
// const key = CryptoJS.SHA256(secret_key).toString().substring(0,32);
// const iv = CryptoJS.SHA256(secret_iv).toString().substring(0, 16);
const secret_key = "<?php echo SECRET_KEY; ?>";
const secret_iv = "<?php echo SECRET_IV; ?>";
const key = CryptoJS.enc.Utf8.parse(CryptoJS.SHA256(secret_key)).toString().substring(0, 32);
const iv = CryptoJS.enc.Utf8.parse(CryptoJS.SHA256(secret_iv)).toString().substring(0, 16);
function aesDecrypt(data) {
let cipher = CryptoJS.AES.decrypt(data, key, { // test case 1
iv: iv,
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.Pkcs7
});
// return cipher.toString(CryptoJS.enc.Utf8);
return cipher.toString();
}
const decrypted = aesDecrypt(base64Decoded);
const collectionInJS = {
base64Decoded: base64Decoded,
encrypted: encrypted,
key: key,
iv: iv,
secret_key: secret_key,
secret_iv: secret_iv,
decrypted: decrypted
}
document.querySelector('.php.secret-key').innerHTML = collectionInPhp.secret_key;
document.querySelector('.js.secret-key').innerHTML = collectionInJS.secret_key;
document.querySelector('.php.secret-iv').innerHTML = collectionInPhp.secret_iv;
document.querySelector('.js.secret-iv').innerHTML = collectionInJS.secret_iv;
document.querySelector('.php.key').innerHTML = collectionInPhp.key;
document.querySelector('.js.key').innerHTML = collectionInJS.key;
document.querySelector('.php.iv').innerHTML = collectionInPhp.iv;
document.querySelector('.js.iv').innerHTML = collectionInJS.iv;
document.querySelector('.php.beforeBase64Encode').innerHTML = collectionInPhp.before_base64_encode;
document.querySelector('.js.afterBase64Decode').innerHTML = collectionInJS.base64Decoded;
document.querySelector('.php.encrypted').innerHTML = collectionInPhp.after_base64_encode;
document.querySelector('.js.encrypted').innerHTML = collectionInJS.encrypted;
document.querySelector('.js.decrypted').innerHTML = collectionInJS.decrypted;
</script>
</body>
</html>
PHP 和 JS 的价值比较
在 PHP 代码中,密钥生成为密码
$secret_key
的 SHA256 哈希值。 hash()
默认返回十六进制编码的结果,这就是为什么结果由 64 个十六进制数字组成,即 64 个字节。AES-256-CBC
隐式地将密钥长度截断为 32 字节。 CryptoJS 不会自动缩短,即缩短必须明确完成。
IV 生成为密码
$secret_iv
的 SHA256 哈希。与密钥不同,它被正确截断为正确的大小。
为了让 CryptoJS 将密钥材料解释为密钥,它必须作为 WordArray 传递。为此,必须使用 UTF-8 编码器转换密钥。这同样适用于 IV.
const secret_key = '$6Za+?k}^`5q:f^@TSxy69gf7JcKuF!,';
const secret_iv = '$6Za+?k}^`5q:f^@TSxy69gf7JcKuF!,';
const text = 'alaks@123';
const keyHex = CryptoJS.SHA256(secret_key).toString().substring(0,32); // Fix 1: Truncate the key to 32 bytes
const ivHex = CryptoJS.SHA256(secret_iv).toString().substring(0, 16);
const key = CryptoJS.enc.Utf8.parse(keyHex); // Fix 2: Convert key and IV to a WordArray using the UTF8 encoder
const iv = CryptoJS.enc.Utf8.parse(ivHex);
function aesEncrypt(data) {
let cipher = CryptoJS.AES.encrypt(data, key, {
iv: iv,
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.Pkcs7
});
return btoa(cipher.toString());
}
function aesDecrypt(data) {
let cipher = CryptoJS.AES.decrypt(atob(data), key, {
iv: iv,
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.Pkcs7
});
return cipher.toString(CryptoJS.enc.Utf8);
}
const ciphertext = aesEncrypt('alaks@123');
const decrypted = aesDecrypt(ciphertext);
console.log(ciphertext); // Rko0Q0lsdnl3Ukp6RkI2bXA4STF6QT09
console.log(decrypted); // alaks@123
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.1.1/crypto-js.min.js"></script>
PHP代码有一些漏洞和低效:
基于@Topaco 的回答,下面是完整的工作代码。
<?php
$text = 'alaks@123';
define('SECRET_KEY', '$6Za+?k}^`5q:f^@TSxy69gf7JcKuF!,');
define('SECRET_IV', '$6Za+?k}^`5q:f^@TSxy69gf7JcKuF!,');
function encrypt_decrypt($action, $string)
{
$collectionInPhp = [];
$encrypt_method = "AES-256-CBC";
$output = false;
$key = hash('sha256', SECRET_KEY);
$iv = substr(hash('sha256', SECRET_IV), 0, 16);
$collectionInPhp['secret_key'] = SECRET_KEY;
$collectionInPhp['secret_iv'] = SECRET_IV;
$collectionInPhp['key'] = $key;
$collectionInPhp['iv'] = $iv;
if ($action == 'encrypt') {
$output = openssl_encrypt($string, $encrypt_method, $key, 0, $iv);
$collectionInPhp['before_base64_encode'] = $output;
$output = base64_encode($output);
$collectionInPhp['after_base64_encode'] = $output;
} else if ($action == 'decrypt') {
$output = openssl_decrypt(base64_decode($string), $encrypt_method, $key, 0, $iv);
}
return [$output, $collectionInPhp];
}
$result = encrypt_decrypt('encrypt', $text);
$encrypted = $result[0];
$collectionInPhp = $result[1];
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/pure/3.0.0/pure-min.css"
integrity="sha512-X2yGIVwg8zeG8N4xfsidr9MqIfIE8Yz1It+w2rhUJMqxUwvbVqC5OPcyRlPKYOw/bsdJut91//NO9rSbQZIPRQ=="
crossorigin="anonymous" referrerpolicy="no-referrer" />
</head>
<body>
<table class="pure-table pure-table-bordered">
<thead>
<tr>
<th>#</th>
<th>PHP</th>
<th>JS</th>
</tr>
</thead>
<tbody>
<tr>
<td>Secret Key</td>
<td class="php secret-key"></td>
<td class="js secret-key"></td>
</tr>
<tr>
<td>Secret IV</td>
<td class="php secret-iv"></td>
<td class="js secret-iv"></td>
</tr>
<tr>
<td>Key</td>
<td class="php key"></td>
<td class="js key"></td>
</tr>
<tr>
<td>IV</td>
<td class="php iv"></td>
<td class="js iv"></td>
</tr>
<tr>
<td>Before Base64Encode</td>
<td class="php beforeBase64Encode"></td>
<td class="js"></td>
</tr>
<tr>
<td>Encrypted</td>
<td class="php encrypted"></td>
<td class="js"></td>
</tr>
<tr>
<td>Received in JS</td>
<td class="php"></td>
<td class="js encrypted"></td>
</tr>
<tr>
<td>After Base64Decode</td>
<td class="php"></td>
<td class="js afterBase64Decode"></td>
</tr>
<tr>
<td>Decrypted</td>
<td class="php"></td>
<td class="js decrypted"></td>
</tr>
</tbody>
</table>
<script src="node_modules/crypto-js/crypto-js.js"></script>
<script>
const collectionInPhp = <?php echo json_encode($collectionInPhp); ?>;
const encrypted = "<?php echo $encrypted; ?>";
const base64Decoded = atob("<?php echo $encrypted; ?>");
const secret_key = "<?php echo SECRET_KEY; ?>";
const secret_iv = "<?php echo SECRET_IV; ?>";
const key = CryptoJS.enc.Utf8.parse(CryptoJS.SHA256(secret_key).toString().substring(0, 32));
const iv = CryptoJS.enc.Utf8.parse(CryptoJS.SHA256(secret_iv).toString().substring(0, 16));
function aesDecrypt(data) {
let cipher = CryptoJS.AES.decrypt(data, key, { // test case 1
iv: iv,
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.Pkcs7
});
return cipher.toString(CryptoJS.enc.Utf8);
// return cipher.toString();
}
const decrypted = aesDecrypt(base64Decoded);
const collectionInJS = {
base64Decoded: base64Decoded,
encrypted: encrypted,
key: key,
iv: iv,
secret_key: secret_key,
secret_iv: secret_iv,
decrypted: decrypted
}
document.querySelector('.php.secret-key').innerHTML = collectionInPhp.secret_key;
document.querySelector('.js.secret-key').innerHTML = collectionInJS.secret_key;
document.querySelector('.php.secret-iv').innerHTML = collectionInPhp.secret_iv;
document.querySelector('.js.secret-iv').innerHTML = collectionInJS.secret_iv;
document.querySelector('.php.key').innerHTML = collectionInPhp.key;
document.querySelector('.js.key').innerHTML = collectionInJS.key;
document.querySelector('.php.iv').innerHTML = collectionInPhp.iv;
document.querySelector('.js.iv').innerHTML = collectionInJS.iv;
document.querySelector('.php.beforeBase64Encode').innerHTML = collectionInPhp.before_base64_encode;
document.querySelector('.js.afterBase64Decode').innerHTML = collectionInJS.base64Decoded;
document.querySelector('.php.encrypted').innerHTML = collectionInPhp.after_base64_encode;
document.querySelector('.js.encrypted').innerHTML = collectionInJS.encrypted;
document.querySelector('.js.decrypted').innerHTML = collectionInJS.decrypted;
</script>
</body>
</html>