如何解决 PHP 中的 OpenSSL "EVP_DecryptFinal_ex:bad decrypt" 错误?
How can I solve this OpenSSL "EVP_DecryptFinal_ex:bad decrypt" error in PHP?
设置:
session_start();
function set_encryption_method() {
if (isset($_SESSION['last_activity']) && (time() - $_SESSION['last_activity']) > 3600) {
unset($_SESSION['cipher']);
unset($_SESSION['iv']);
unset($_SESSION['last_activity']);
}
$cipher = 'aes-256-cbc';
$iv = random_bytes(16);
if (in_array($cipher, openssl_get_cipher_methods())) {
if (!isset($_SESSION['cipher'])) {
$_SESSION['cipher'] = $cipher;
}
if (!isset($_SESSION['iv'])) {
$_SESSION['iv'] = $iv;
}
$_SESSION['last_activity'] = time();
} else {
die('Encryption method not supported!');
}
}
set_encryption_method();
加密:
function encrypt_string($key, $string) {
// $key is a constant stored in a database
return rawurlencode(base64_encode(openssl_encrypt($string, $_SESSION['cipher'], $key, 0, $_SESSION['iv'])));
}
解密:
function decrypt_string($key, $encrypted) {
// $key is a constant stored in a database
return openssl_decrypt(rawurldecode(base64_decode($encrypted)), $_SESSION['cipher'], $key, 0, $_SESSION['iv']);
}
当使用适当的参数调用 decrypt_string()
时,它会抛出此错误:digital envelope routines evp_decrypt_final_ex: bad decrypt
。如果我对 iv 进行硬编码,那么它可以正常工作。
我做错了什么?
要解决的第一点:您为什么使用 URL encode/decode?
那些是为了通过浏览器传递参数;并且浏览器通常会为您解码,因此作为基本规则 - 如果您在 PHP 中编写了“urldecode”,那么您可能已经出错了,因为您可能已经在解码某些东西解码。 (或者没有用在正确的地方,或者在不需要的时候使用)
然而,Base64 是(或应该是)url-安全的,所以我建议敲掉 urlencode/decode 看看会发生什么。
第二点解决:您需要按照您设置的相反顺序撤消urlencode/base64转换。
所以 rawurlencode(base64_encode(openssl_encrypt(
反转为 openssl_decrypt(base64_encode(rawurldecode
(你翻转了 base64 和 url 解码)。但是完全删除 urlencode/decode(除非你坚持认为你需要它)也会纠正这个问题。
错误消息是(间接地)由于您使用不同的 IV 进行加密和解密而导致的。从你的描述中不清楚这是怎么发生的,但让我提出一些建议来完全避免你的问题。
首先,对于 EAS-CBC,多次使用相同的 IV + 组合键不是一个好主意。您可以在 Is AES in CBC mode secure if a known and/or fixed IV is used? 的答案中找到一些相关讨论。您没有提到如何使用不同的功能,但在 3600 秒内,您使用的是相同的 IV + 组合键。
要解决此问题,您可以为您执行的每个加密生成一个随机 IV。然后您可以将 IV 与加密数据一起存储; IV 不是必需的,也不应该是秘密的。以下两个函数是对您的函数的修改,通过在加密后连接两个函数并在解密前拆分两个函数来实现这一点:
function encrypt_string($key, $string) {
// $key is a constant stored in a database
$iv = random_bytes(16);
$ciphtxt = openssl_encrypt($string, $_SESSION['cipher'], $key, OPENSSL_RAW_DATA, $iv);
return base64_encode($iv.$ciphtxt);
}
function decrypt_string($key, $encrypted) {
// $key is a constant stored in a database
$combo = base64_decode($encrypted);
$iv = substr($combo, 0, 16);
$ciphtxt= substr($combo, 16);
return openssl_decrypt($ciphtxt, $_SESSION['cipher'], $key, OPENSSL_RAW_DATA, $iv);
}
注意标志的使用 OPENSSL_RAW_DATA
。正如 the documentation for openssl_encrypt 提到的(不太清楚),如果您不提供该标志,结果将是 base64 编码的。你自己在做 base64,所以我添加了标志。这也使得处理 IV 和密文的(去)连接变得更容易一些。
关于 bad decrypt
错误的发生的几句话:当填充字节与预期不同时发出。由于您使用了不正确的 IV 进行解密,因此 AES 解密没有生成原始数据,因此填充字节很可能没有正确的值。
设置:
session_start();
function set_encryption_method() {
if (isset($_SESSION['last_activity']) && (time() - $_SESSION['last_activity']) > 3600) {
unset($_SESSION['cipher']);
unset($_SESSION['iv']);
unset($_SESSION['last_activity']);
}
$cipher = 'aes-256-cbc';
$iv = random_bytes(16);
if (in_array($cipher, openssl_get_cipher_methods())) {
if (!isset($_SESSION['cipher'])) {
$_SESSION['cipher'] = $cipher;
}
if (!isset($_SESSION['iv'])) {
$_SESSION['iv'] = $iv;
}
$_SESSION['last_activity'] = time();
} else {
die('Encryption method not supported!');
}
}
set_encryption_method();
加密:
function encrypt_string($key, $string) {
// $key is a constant stored in a database
return rawurlencode(base64_encode(openssl_encrypt($string, $_SESSION['cipher'], $key, 0, $_SESSION['iv'])));
}
解密:
function decrypt_string($key, $encrypted) {
// $key is a constant stored in a database
return openssl_decrypt(rawurldecode(base64_decode($encrypted)), $_SESSION['cipher'], $key, 0, $_SESSION['iv']);
}
当使用适当的参数调用 decrypt_string()
时,它会抛出此错误:digital envelope routines evp_decrypt_final_ex: bad decrypt
。如果我对 iv 进行硬编码,那么它可以正常工作。
我做错了什么?
要解决的第一点:您为什么使用 URL encode/decode?
那些是为了通过浏览器传递参数;并且浏览器通常会为您解码,因此作为基本规则 - 如果您在 PHP 中编写了“urldecode”,那么您可能已经出错了,因为您可能已经在解码某些东西解码。 (或者没有用在正确的地方,或者在不需要的时候使用)
然而,Base64 是(或应该是)url-安全的,所以我建议敲掉 urlencode/decode 看看会发生什么。
第二点解决:您需要按照您设置的相反顺序撤消urlencode/base64转换。
所以 rawurlencode(base64_encode(openssl_encrypt(
反转为 openssl_decrypt(base64_encode(rawurldecode
(你翻转了 base64 和 url 解码)。但是完全删除 urlencode/decode(除非你坚持认为你需要它)也会纠正这个问题。
错误消息是(间接地)由于您使用不同的 IV 进行加密和解密而导致的。从你的描述中不清楚这是怎么发生的,但让我提出一些建议来完全避免你的问题。
首先,对于 EAS-CBC,多次使用相同的 IV + 组合键不是一个好主意。您可以在 Is AES in CBC mode secure if a known and/or fixed IV is used? 的答案中找到一些相关讨论。您没有提到如何使用不同的功能,但在 3600 秒内,您使用的是相同的 IV + 组合键。
要解决此问题,您可以为您执行的每个加密生成一个随机 IV。然后您可以将 IV 与加密数据一起存储; IV 不是必需的,也不应该是秘密的。以下两个函数是对您的函数的修改,通过在加密后连接两个函数并在解密前拆分两个函数来实现这一点:
function encrypt_string($key, $string) {
// $key is a constant stored in a database
$iv = random_bytes(16);
$ciphtxt = openssl_encrypt($string, $_SESSION['cipher'], $key, OPENSSL_RAW_DATA, $iv);
return base64_encode($iv.$ciphtxt);
}
function decrypt_string($key, $encrypted) {
// $key is a constant stored in a database
$combo = base64_decode($encrypted);
$iv = substr($combo, 0, 16);
$ciphtxt= substr($combo, 16);
return openssl_decrypt($ciphtxt, $_SESSION['cipher'], $key, OPENSSL_RAW_DATA, $iv);
}
注意标志的使用 OPENSSL_RAW_DATA
。正如 the documentation for openssl_encrypt 提到的(不太清楚),如果您不提供该标志,结果将是 base64 编码的。你自己在做 base64,所以我添加了标志。这也使得处理 IV 和密文的(去)连接变得更容易一些。
关于 bad decrypt
错误的发生的几句话:当填充字节与预期不同时发出。由于您使用了不正确的 IV 进行解密,因此 AES 解密没有生成原始数据,因此填充字节很可能没有正确的值。