用 openssl 解密 mcrypt

Decrypt mcrypt with openssl

由于 mcrypt 被认为已过时,我的任务是升级当前代码以使用 openssl。听起来很简单,但是......经过几天的尝试和失败,我觉得自己要疯了。

我想问你的问题是:有什么方法可以用 openssl 解密以前用 mcrypt 加密的数据吗?我已经阅读了很多关于这个问题的帖子,其中大部分都说在 运行 mcrypt 之前需要手动填充数据 was/is。 问题是 mcrypt-ed 数据已经加密(使用 mcrypt 提供的自动空填充)并驻留在数据库中,因此无法对其进行修改and/or。

提及:

  1. 使用的算法是带有 32 字节密钥的 rijndael-128 cbc(所以我对 openssl 使用 aes-256-cbc)。
  2. 我正在为 php(php-crypto)使用 openssl 包装器。
  3. 我已经设法通过简单地剥离结束解码字符(如果它们不是字母数字)来进行逆运算(使用 mcrypt 解码 openssl)。
  4. 在 mcrypt-ing 之前手动填充数据,然后使用 openssl 对其进行解密非常有效,但这不是这里的问题。

一些代码片段:

// Simple mcrypt encrypt, decrypt with php-crypto example
// This doesn't work and produces a "Finalizing of cipher failed" error
        $data = "This is a text";
        $strMcryptData=mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $key, $data, MCRYPT_MODE_CBC, $iv);

        $algorithm = 'aes-256-cbc';
        $cipher = new Cipher($algorithm);
        $sim_text = $cipher->decrypt($strMcryptData, $key, $iv);

// Simple mcrypt encrypt with padding, decrypt with php-crypto
// Works and produces the correct text on decryption
        $pad =  $blocksize - (strlen($data) % $blocksize);
        $text = $data;
        $text .= str_repeat(chr($pad), $pad);
        $strPaddedData=mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $key, $text, MCRYPT_MODE_CBC, $iv);

        $sim_text = $cipher->decrypt($strPaddedData, $key, $iv);

应该没有任何重大差异除了填充。如果您直接使用更高级别的 OpenSSL (EVP) 构造,您应该能够调用 EVP_CIPHER_CTX_set_padding。我认为填充参数应该为零,尽管 it is not documented。为此,您需要一个预先配置的 encryption/decryption 上下文。

之后你将得到与密文长度相同的明文。末尾的零到十五个字节将被设置为零。您需要手动删除这些字节。如果明文恰好以零字节结尾,那么这些也将被删除;但是,如果明文是可打印字符串(使用 8 位编码),情况就不会如此。您可能需要确保删除的字节数不超过 15 个。

如果您获得完全随机的明文,则您的密钥或密文不正确。如果您获得可读的明文但对于前 16 个字节,则您的 IV 处理不正确。

如果您在 mcrypt 中加密而不手动添加 PKCS7,mcrypt 会很乐意用 NUL 字节填充您的明文。

每当使用 aes-X-cbc 时,OpenSSL 都会为您 执行 PKCS7 填充 。不幸的是,如果您有 AES-CBC(NULL_PADDED(plaintext)) 并尝试解密它,openssl_decrypt 将尝试删除填充并失败。

比较http://3v4l.org/bdQe9 vs http://3v4l.org/jr68f and http://3v4l.org/K6ZEU

OpenSSL 扩展目前不提供让您说出 "This string is not padded, please don't strip the padding for me" 然后自行删除 NUL 字节的方法。您必须使用 PKCS7 填充进行加密才能成功解密。

虽然这是 OpenSSL 的限制,但值得强调的是,您 运行 进入它的唯一原因是因为 mcrypt is terrible.

有点旧,但你可以通过一些工作来解决这个问题。你 可以 告诉 PHP 的 OpenSSL 加密的字符串没有被填充,并告诉它给你原始输出(所以你不必 base64 解码它,任何一个)。然后,如果字符串的长度恰好可以被 IV 整除,则可以从结果字符串的末尾去除空值(这是一个完整性检查,就好像结果字符串不能被 IV 整除那么它不是完全填充)。

请注意,此代码有两个主要限制

  1. 如果您在任何时候加密了一个以两个或更多 NULL 字节结尾的合法字符串,那么此代码将不会为您提供相同的输出。

  2. 如果字符串的填充只需要一个空字节,那么这段代码不会去除它。

如果您知道 FACT 您没有加密任何以空字节结尾的任何内容,则可以解决这两个问题,您可以更改将空值剥离为的代码只是做一个 preg_replace;只需确保将正则表达式锚定到字符串的末尾,这样它只会从末尾删除。

<?php
$message = 'test';
$key = openssl_random_pseudo_bytes(16);
$iv = openssl_random_pseudo_bytes(16);

$cipher = mcrypt_encrypt(
    MCRYPT_RIJNDAEL_128,
    $key,
    $message,
    MCRYPT_MODE_CBC,
    $iv
);

$plain = openssl_decrypt(
    $cipher,
    'aes-128-cbc',
    $key,
    OPENSSL_RAW_DATA | OPENSSL_NO_PADDING,
    $iv
);

//try to detect null padding
if (mb_strlen($iv, '8bit') % mb_strlen($plain, '8bit') == 0) {
        preg_match_all('#([[=10=]]+)$#', $plain, $matches);
        if (mb_strlen($matches[1][0], '8bit') > 1) {
                $plain = rtrim($plain, "[=10=]");
                trigger_error('Detected and stripped null padding. Please double-check results!');
        }
}



var_dump(
    $message,
    bin2hex($cipher),
    $plain,
    mb_strlen($message, '8bit'),
    mb_strlen($plain, '8bit'),
    $message === $plain
);

http://3v4l.org/kYAXn

显然此代码没有主要免责声明,请在您的用例中对其进行测试,但希望有人会发现它有用。