【问题标题】:Decrypt mcrypt with openssl使用 openssl 解密 mcrypt
【发布时间】:2015-10-09 19:13:14
【问题描述】:

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

我的问题是:有什么方法可以用以前用 mcrypt 加密的 openssl 数据解密?我已经阅读了很多关于这个问题的帖子,其中大多数人说在运行 mcrypt 之前需要/是以前的手动填充数据。 问题是 mcrypt 数据已经加密(使用 mcrypt 提供的自动空填充)并驻留在数据库中,因此不可能和/或不希望对其进行修改。

提及:

  1. 使用的算法是带有 32 字节密钥的 rijndael-128 cbc(所以我在 openssl 中使用 aes-256-cbc)。
  2. 我正在为 php (php-crypto) 使用 openssl 包装器。
  3. 我已经成功地进行了逆运算(使用 mcrypt 解码 openssl),如果它们是非字母数字字符,则只需剥离结束的解码字符。
  4. 在 mcrypt-ing 之前手动填充数据,然后使用 openssl 解密它就像一个魅力,但这不是这里的问题。

部分代码sn-ps:

// 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);

【问题讨论】:

标签: php encryption openssl cryptography mcrypt


【解决方案1】:

有点旧,但你可以通过一些工作来解决这个问题。你可以告诉 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('#([\0]+)$#', $plain, $matches);
        if (mb_strlen($matches[1][0], '8bit') > 1) {
                $plain = rtrim($plain, "\0");
                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

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

【讨论】:

  • 在 PHP5.6、PHP7 和 PHP7.1 中完美运行。非常感谢您的工作!
  • 感谢您的回答,非常适合我!
【解决方案2】:

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

无论何时使用aes-X-cbc,OpenSSL 都会为您执行 PKCS7 填充。这样做的不幸后果是,如果您有 AES-CBC(NULL_PADDED(plaintext)) 并尝试对其进行解密,openssl_decrypt 将尝试删除填充并失败。

比较 http://3v4l.org/bdQe9http://3v4l.org/jr68fhttp://3v4l.org/K6ZEU

OpenSSL 扩展目前没有为您提供一种方式来表示“此字符串未填充,请不要为我去除填充”,然后自行删除 NUL 字节。您必须使用 PKCS7 填充进行加密才能成功解密。

虽然这是 OpenSSL 的一个限制,但需要强调的是,您遇到它的唯一原因是因为 mcrypt is terrible

【讨论】:

    【解决方案3】:

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

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

    如果你得到完全随机的明文,那么你的密钥或密文是不正确的。如果您获得可读的纯文本但前 16 个字节,那么您的 IV 处理不正确。

    【讨论】:

    • 我不确定此方法是否可以通过 PHP 包装器 API 使用,您可能需要调用本机代码。
    • 不幸的是,你必须重新实现你的 mcrypt 加密函数来强制执行 PKCS7 填充,以便 openssl 成功解密。
    • @ScottArciszewski 填充选项还没有实现吗?它们存在于 API...: options can be one of OPENSSL_RAW_DATA, OPENSSL_ZERO_PADDING
    猜你喜欢
    • 1970-01-01
    • 2019-04-13
    • 2014-02-24
    • 2017-07-05
    • 2018-01-18
    • 2013-12-28
    • 2018-08-02
    • 2012-06-10
    相关资源
    最近更新 更多