用 OpenSSL 替换 Mcrypt

Posted

技术标签:

【中文标题】用 OpenSSL 替换 Mcrypt【英文标题】:Replace Mcrypt with OpenSSL 【发布时间】:2012-04-17 03:29:04 【问题描述】:

目前我们在我们的系统上安装了 mcrypt 来加密我们 php 应用程序中的一些敏感数据。现在我们有一个新要求,我们必须将 crypt 模块更改为 openssl。另一件重要的事情是我们正在使用密码河豚和模式 ecb。所以我开始测试有什么区别以及如何用openssl解密mcrypt加密字符串。

我使用了标准的PHP函数:

mcrypt_encrypt 与 openssl_encrypt mcrypt_decrypt 与 openssl_decrypt

两种方法都提供不同的结果。第二件事是,在给定的密码 (blowfish) 和模式 (ecb) 中,两种类型都需要不同的 IV 长度(openssl=0 和 mcrypt=56)。

有人知道我如何无需大量迁移工作即可轻松更改模块吗?

提前致谢!

更新:

这是我测试过的代码:

<?php 

function say($message)
    if(!is_string($message))
        if(!isset($_SERVER["HTTP_USER_AGENT"])) echo "<pre>";
        echo var_export($message, true) . ((!isset($_SERVER["HTTP_USER_AGENT"]) ? "\n" : "<br />"));
        if(!isset($_SERVER["HTTP_USER_AGENT"])) echo "</pre>";
    else
        echo $message . ((!isset($_SERVER["HTTP_USER_AGENT"]) ? "\n" : "<br />"));
    


say("= Begin raw encryption");
$key    = "anotherpass";
$str    = "does it work";

say("  Params:");
say("  - String to encrypt '".$str."'");
say("  - Key: ".$key);
say("");


$params = array(
    "openssl"  => array(
        "cipher"    => "BF",
        "mode"      => "ECB",
    ),
    "mcrypt" => array(
        "cipher"    => "blowfish", 
        "mode"      => "ecb",
    ),
);

say("= Mcrypt");
$handler = mcrypt_module_open($params['mcrypt']['cipher'], '', $params['mcrypt']['mode'], '');
$iv      = mcrypt_create_iv (mcrypt_enc_get_iv_size($handler), MCRYPT_RAND);
$keysize = mcrypt_enc_get_key_size($handler);
mcrypt_generic_init($handler,$key,"\0\0\0\0\0\0\0\0");
say("  Params:");
say("  - InitVector   ".bin2hex($iv)." (bin2hex)");
say("  - Max keysize  ".$keysize);
say("  - Cipher       ".$params['mcrypt']['cipher']);
say("  - Mode         ".$params['mcrypt']['mode']);
say("");
say("  Encryption:");
$m_encrypted = mcrypt_generic($handler, $str);
$m_decrypted = mdecrypt_generic($handler, $m_encrypted);
say("  - Encrypted   ".bin2hex($m_encrypted)." (bin2hex)");
say("  - Descrypted  ".$m_decrypted);
say("");


say("= Openssl");
say("  Params:");
say("  - InitVector   not needed");
say("  - Max keysize  ".openssl_cipher_iv_length($params['openssl']['cipher']."-".$params['openssl']['mode']));
say("  - Cipher       ".$params['openssl']['cipher']);
say("  - Mode         ".$params['openssl']['mode']);
say("");
say("  Encryption:");
$o_encrypted = openssl_encrypt($str,$params['openssl']['cipher']."-".$params['openssl']['mode'],$key,true);
$o_decrypted = openssl_decrypt($o_encrypted,$params['openssl']['cipher']."-".$params['openssl']['mode'],$key,true);
say("  - Encrypted   ".bin2hex($o_encrypted)." (bin2hex)");
say("  - Descrypted  ".$o_decrypted);

这是我的结果:

= Begin raw encryption
  Params:
  - String to encrypt 'does it work'
  - Key: anotherpass

= Mcrypt
  Params:
  - InitVector   06a184909d7bf863 (bin2hex)
  - Max keysize  56
  - Cipher       blowfish
  - Mode         ecb

  Encryption:
  - Encrypted   0e93dce9a6a88e343fe5f90d1307684c (bin2hex)
  - Descrypted  does it work

= Openssl
  Params:
  - InitVector   not needed
  - Max keysize  0
  - Cipher       BF
  - Mode         ECB

  Encryption:
  - Encrypted   213460aade8f9c14d8d51947b8231439 (bin2hex)
  - Descrypted  does it work

现在有什么想法吗?

谢谢!

【问题讨论】:

你要么必须运行一个迁移脚本,使用 mcrypt 解密当前数据,然后使用 openssl 再次加密,否则你需要实现一个你知道哪个加密/用于每一项数据的解密函数,并在下次访问使用 mcrypt 加密的某些数据时根据需要将它们从 mcrypt 更改为 openssl。 据我了解,mcrypt 和 open_ssl 使用不同的密钥派生方法,所以 Jon 是对的,您需要通过解密迁移然后加密或标记数据,以便在下次访问时迁移. 我想知道为什么ECB模式下需要IB。参见en.wikipedia.org/wiki/Block_cipher_modes_of_operation对ECB模式的描述。 另见Upgrading my encryption library from Mcrypt to OpenSSL和Preparing for removal of Mcrypt in PHP 7.2 【参考方案1】:

Blowfish 是分组密码。它要求在加密之前填充数据。 OpenSSL 使用 PKCS#7,而 mcrypt 使用 PKCS#5。数据的不同填充算法。 最小 PKCS#5 填充长度为 0,对于 PKCS#7,它为 1 (wikipedia)。看看这个例子(我已经手动填充了mcrypt_encrypt() PKCS#7 风格的输入数据):

<?php 

$key = "anotherpassword1";
$str = "does it work 12";

$enc = mcrypt_encrypt(MCRYPT_BLOWFISH, $key, $str."\1", MCRYPT_MODE_ECB);
$dec = mcrypt_decrypt(MCRYPT_BLOWFISH, $key, $enc, MCRYPT_MODE_ECB);
echo(bin2hex($enc).PHP_EOL);
var_dump($dec);

$enc = openssl_encrypt($str, 'bf-ecb', $key, true);
$dec = openssl_decrypt($enc, 'bf-ecb', $key, true);
echo(bin2hex($enc).PHP_EOL);
var_dump($dec);

?>

无法打开使用 mcrypt_encrypt() 加密的数据,除非在调用 mcrypt_encrypt() 之前使用 PKCS#7 进行了手动数据填充。

在您的情况下只有一种方法 - 重新加密数据。

PS:您的源中有错误 - ECB 模式根本不使用 IV (wikipedia)

【讨论】:

谢谢,这个提示帮助我使 Rijndael-128/AES-128 在 MCrypt 和 OpenSSL 之间兼容。不幸的是,它不适用于 Blowfish - 事实证明它确实适用于 Blowfish,但密钥大小必须至少为 16 字节(Blowfish 的***文章说它支持较少)。 这是因为 PKCS#7 中的填充 @clover PKCS#7 和 PKCS#5 本质上是一样的,在实现上也是一样的,只是懒开发者重用了PKCS#5标识符,而不是添加一个PKCS#7标识符.请修复答案并发表评论。请参阅PKCS#7 padding:PKCS#5 填充与 PKCS#7 填充相同,只是它仅针对使用 64 位(8 字节)块大小的块密码定义。 实际上两者可以互换使用 @clover 另见:What is the difference between PKCS#5 padding and PKCS#7 padding:许多密码库使用指示 PKCS#5 或 PKCS#7 的标识符来定义相同的填充机制。【参考方案2】:

对于较短的密钥,您应该在迁移 mcrypt 的河豚时为 openssl 制作循环密钥。

function make_openssl_blowfish_key($key)

    if("$key" === '')
        return $key;

    $len = (16+2) * 4;
    while(strlen($key) < $len) 
        $key .= $key;
    
    $key = substr($key, 0, $len);
    return $key;

见:https://bugs.php.net/bug.php?id=72362

见:Moving from mcrypt with Blowfish & ECB to OpenSSL

【讨论】:

【参考方案3】:

@clover 是正确的,Blowfish 的默认填充在 mcrypt 和 Openssl 之间是不同的,但它不能做到是错误的。如果您使用OPENSSL_ZERO_PADDING 选项进行解密,则两者实际上是兼容的:

openssl_decrypt($data, 'bf-ecb', $key, OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING);

【讨论】:

OPENSSL_ZERO_PADDING 不添加任何填充,任何非标准填充都需要在加密之前手动添加并在解密时删除。从文档openssl_encrypt,也许您使用的是不同的版本。来自 doc cmets:因此,OPENSSL_ZERO_PADDING 禁用了上下文的填充,这意味着您必须手动将自己的填充应用于块大小。不使用 OPENSSL_ZERO_PADDING,您将自动获得 PKCS#7 填充。 @zaph 也许您应该在投反对票之前尝试一下代码?为我工作。【参考方案4】:

如果您想使用 openssl 加密并且在使用 mcrypt 解密时仍然获得与使用 mcrypt 加密一样的结果,您需要在使用 openssl_encrypt 加密之前手动填充输入字符串并传递 @ 987654321@ 选项。

$str = 'encrypt me';
$cipher = 'AES-256-CBC';
$key = '01234567890123456789012345678901';
$opts = OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING;
$iv_len = 16;
$str_len = mb_strlen($str, '8bit');
$pad_len = $iv_len - ($str_len % $iv_len);
$str .= str_repeat(chr(0), $pad_len);
$iv = openssl_random_pseudo_bytes($iv_len);


$encrypted = openssl_encrypt($str, $cipher, $key, $opts, $iv);

然后使用 mcrypt_decrypt 进行解密就像您也使用 mcrypt 进行加密一样。

mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $key, $encrypted, MCRYPT_MODE_CBC, $iv)

【讨论】:

以上是关于用 OpenSSL 替换 Mcrypt的主要内容,如果未能解决你的问题,请参考以下文章

PHP - 用 openssl_random_pseudo_bytes() 替换 mcrypt_create_iv()

php怎么开启openssl模块

PHP加密扩展库-openssl

使用特定的openssl版本构建Qt

用 openSSL 生成 公钥 私钥

用openssl生成的ssl证书和付费的有啥区别