Mcrypt被弃用后如何解密?

4

我已将我的php版本更新到7.1。

我以前使用mcrypt加密数据的函数现在被弃用了。

那么,如何在不回退到旧版本的php的情况下解密数据呢?

以下是我使用的代码:

public function encrypt($plaintext) {
    $ivSize = mcrypt_get_iv_size(self::CIPHER, self::MODE);
    $iv = mcrypt_create_iv($ivSize, MCRYPT_DEV_URANDOM);
    $ciphertext = mcrypt_encrypt(self::CIPHER, $this->key, $plaintext, self::MODE, $iv);
    return base64_encode($iv.$ciphertext);
}

public function decrypt($ciphertext) {
    $ciphertext = base64_decode($ciphertext);
    $ivSize = mcrypt_get_iv_size(self::CIPHER, self::MODE);
    if (strlen($ciphertext) < $ivSize) {
        throw new Exception('Missing initialization vector');
    }

    $iv = substr($ciphertext, 0, $ivSize);
    $ciphertext = substr($ciphertext, $ivSize);
    $plaintext = mcrypt_decrypt(self::CIPHER, $this->key, $ciphertext, self::MODE, $iv);
    return rtrim($plaintext, "\0");
}

使用常量:

const CIPHER = MCRYPT_RIJNDAEL_128; // Rijndael-128 is AES
const MODE   = MCRYPT_MODE_CBC;

我看到建议使用OpenSSL,从现在开始我会使用它。但是如何使用这种方法解密旧数据呢?

谢谢。

编辑: 我知道我可以使用OpenSSL作为替代方案。 这就是我现在正在做的内容。 但我需要解密我的旧内容中的加密代码。

*@symcbean的编辑请求

尝试使用以下方式使用OpenSSL进行解密:

public function decrypt($ciphertext) {
    $ciphertext = base64_decode($ciphertext);

    if (!function_exists("openssl_decrypt")) {
       throw new Exception("aesDecrypt needs openssl php module.");
    }

$key    = $this->key;
$method = 'AES-256-CBC';
$ivSize = openssl_cipher_iv_length($method);
$iv     = substr($ciphertext,0,$ivSize);
$data   = substr($ciphertext,$ivSize);
$clear  = openssl_decrypt ($data, $method, $key, 'OPENSSL_RAW_DATA'|'OPENSSL_ZERO_PADDING', $iv);

return $clear;
}

1
不是这样的。我不需要替代方案。我知道我可以使用openssl。但我需要解密我的加密内容。 - Puya Sarmidani
4
@Ben: 不同意-那是关于密码的讨论,而被接受的答案并没有解决具体的解密问题。 - symcbean
你尝试过使用openssl解密mcrypt密文吗?你的代码在哪里?发生了什么事? - symcbean
@symcbean:刚刚编辑了我的帖子。 - Puya Sarmidani
1
我认为如果您之前使用了MCRYPT_RIJNDAEL_128,那么您的$method应该是"AES-128-CBC"。另外,openssl_decrypt标志是常量,而不是字符串,因此请删除引号,例如OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING - Joe
显示剩余3条评论
3个回答

2
重要的一点是,如果输入数据不是块大小的倍数,mcrypt_encrypt 会对其进行零填充。如果数据本身具有尾随零,则会导致模棱两可的结果。 openssl_decrypt 不会自动删除零填充,所以您只能修剪尾随的空值。
以下是一个微不足道的示例:
$data = "Lorem ipsum";
$key = "1234567890abcdef";
$iv = "1234567890abcdef";

$encrypted = mcrypt_encrypt(
    MCRYPT_RIJNDAEL_128, $key, $data, MCRYPT_MODE_CBC, $iv);
echo bin2hex($encrypted) . "\n";

$decrypted = openssl_decrypt(
    $encrypted, "AES-128-CBC", $key,
    OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING, $iv);
echo var_export($decrypted, true) . "\n";
$result = rtrim($decrypted, "\0");
echo var_export($result, true) . "\n";

输出:

70168f2d5751b3d3bf36b7e6b8ec5843
'Lorem ipsum' . "\0" . '' . "\0" . '' . "\0" . '' . "\0" . '' . "\0" . ''
'Lorem ipsum'

这给了我一个结果,但似乎错误是由base64_decode引起的。响应是所有未知字符,如“?”等。也许是UTF 8问题?你认为呢? - Puya Sarmidani
你认为这是nginx上base64和HTTP2的问题吗? - Puya Sarmidani

0

我解决了。 不知道这是否是正确的方法(猜测不是) 但是我通过远程连接到一个低版本的PHP服务器上。 解密了所有内容并使用OpenSSL重新加密。

感谢您的建议!


-1

我也遇到了一些问题,尝试使用mcrypt_encrypt加密的数据,然后使用openssl_decrypt进行解密。下面的小测试使用mcrypt和openssl(带有零填充和不带填充)加密一个字符串,并使用两种方法解密所有字符串。此示例使用ECB模式,但如果需要,您可以通过添加IV轻松更改为CBC。

// Setup key and test data
$key = hash("sha256", 'test', true);
$data = 'Hello World';
$enc = $dec = [];
// Encrypt with MCRYPT_RIJNDAEL_128 method
$enc['RIJ'] = base64_encode(mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $key, $data, MCRYPT_MODE_ECB));
// Encrypt with OpenSSL equivalent AES-256
$enc['AES'] = base64_encode(openssl_encrypt($data, 'aes-256-ecb', $key, OPENSSL_RAW_DATA));
// Encrypt with OpenSSL equivalent AES-256 and added zero padding
if (strlen($data) % 8) $data = str_pad($data, strlen($data) + 8 - strlen($data) % 8, "\0");
$enc['AES0'] = base64_encode(openssl_encrypt($data, 'aes-256-ecb', $key, OPENSSL_RAW_DATA | OPENSSL_NO_PADDING));
// Decrypt all strings with MCRYPT_RIJNDAEL_128
$dec['mRIJ'] = bin2hex(mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $key, base64_decode($enc['RIJ']), MCRYPT_MODE_ECB));
$dec['mAES'] = bin2hex(mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $key, base64_decode($enc['AES']), MCRYPT_MODE_ECB));
$dec['mAES0'] = bin2hex(mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $key, base64_decode($enc['AES0']), MCRYPT_MODE_ECB));
// Decrypt all strings with OpenSSL equivalent AES-256
$dec['oRIJ'] = bin2hex(openssl_decrypt(base64_decode($enc['RIJ']), 'aes-256-ecb', $key, OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING));
$dec['oAES'] = bin2hex(openssl_decrypt(base64_decode($enc['AES']), 'aes-256-ecb', $key, OPENSSL_RAW_DATA));
$dec['oAES0'] = bin2hex(openssl_decrypt(base64_decode($enc['AES0']), 'aes-256-ecb', $key, OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING));
// Print results
print_r($enc);
var_dump($dec);

print_rvar_dump的输出如下:

Array
(
    [RIJ] => YcvcTwAMLUMBCZXu5XqoEw==
    [AES] => +AXMBwkWlgM1YDieGgekSg==
    [AES0] => YcvcTwAMLUMBCZXu5XqoEw==
)
array(6) {
  ["mRIJ"]=>
  string(32) "48656c6c6f20576f726c640000000000"
  ["mAES"]=>
  string(32) "48656c6c6f20576f726c640505050505"
  ["mAES0"]=>
  string(32) "48656c6c6f20576f726c640000000000"
  ["oRIJ"]=>
  string(32) "48656c6c6f20576f726c640000000000"
  ["oAES"]=>
  string(22) "48656c6c6f20576f726c64"
  ["oAES0"]=>
  string(32) "48656c6c6f20576f726c640000000000"
}

如果您需要使用openssl方法获得与mcrypt相同的加密字符串,则必须手动向字符串添加零填充(在示例中为AES0)。这样,您将获得与以前完全相同的加密和解密字符串。关于零填充的其他信息,请查看Joe在此处的答案:php: mcrypt_encrypt to openssl_encrypt, and OPENSSL_ZERO_PADDING problems

如果你不想手动为所有新消息添加零填充,你需要为解密旧的 mcrypt 加密消息和使用 openssl 加密的新消息使用不同的标志。对于旧消息,你必须使用 OPENSSL_ZERO_PADDING 标志(在示例中为 $dec['oRIJ']),而对于 openssl 加密的消息,则不能使用它(在示例中为 $dec['oAES'])。在我的情况下,我使用了这种方法,因为openssl的默认行为对我来说似乎更正确,因为只要你用 11 个字节加密一个字符串,解密后得到的字符串就会有 11 个字节。正如你在示例中看到的那样,mcrypt 或 openssl 与添加的零填充不是这种情况。在这些情况下,你需要手动删除尾随的零以获取原始数据。


  1. 这个问题使用了CBC模式和IV,而这个答案没有使用,因此并没有回答这个问题。
  2. “加密字符串对于两种方法是不同的”是一个错误,相同的输入应该得到相同的加密数据。
- zaph
  1. 这个问题是“如何在 Mcrypt 废弃后解密?”这个答案就是在回答这个问题。这个示例可以轻松地调整到任何特定模式。
  2. 这是因为缺少零填充,正如 Joe 的答案已经解释的那样。而且问题是关于解密的,我提供有关加密的额外信息只是“补充”。
- Konrad
考虑升级答案以解决代码中的这些问题。 - zaph

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接