如何解决 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 解密没有生成原始数据,因此填充字节很可能没有正确的值。