将 PHP AES 加密从 mcrypt 迁移到 openssl return 不同的加密字符串

Migrate PHP AES encryption from mcrypt to openssl return different encrypted string

我正在尝试将我们现有的使用 mcrypt 的加密函数更改为 OpenSSL.But 它会创建不同的加密字符串。我们如何解决这个问题?

<?php
$str    = 'test';
$method = 'AES-128-CBC';
$key    = 'o6xSYYAVl2eapPI2';
$iv     = 'fedcba9876543210';

function encrypt_mcrypt($str=NULL,$key,$iv) {
  $td = mcrypt_module_open(MCRYPT_RIJNDAEL_128, '', 'cbc', $iv);

  mcrypt_generic_init($td,$key,$iv);
  $encrypted = mcrypt_generic(@$td,@$str);

  mcrypt_generic_deinit($td);
  mcrypt_module_close($td);

  return bin2hex(@$encrypted);
}
function encrypt_openssl($str=NULL,$key,$iv) {
  $encrypted =openssl_encrypt($str, 'AES-256-CBC', $key, OPENSSL_RAW_DATA,$iv); 
  return bin2hex(@$encrypted);
}


echo 'Mcrypt:'.encrypt_openssl($str,$key,$iv);
echo '<br/>';
echo 'Openssl:'.encrypt_mcrypt($str,$key,$iv);

输出

Mcrypt:0ab40f383b421ba465c0cbbcded97319
Openssl:57c86f3089535b3acfbe65cecbb662b9

两种代码都使用不同的 AES 变体和填充。 mcrypt 代码应用 AES-128 和零填充,openssl 代码应用 AES-256 和 PKCS7 填充。为确保两个密文匹配,两个代码必须使用相同的 AES 变体和填充。

mcrypt 根据密钥大小识别 AES 变体。由于 $key 是一个 16 字节的密钥,因此使用 AES-128。 openssl 根据第二个参数中传递的规范确定 AES 变体。太短的键用 0 值填充到所需的长度,太长的键被截断。这里指定了AES-256-CBC,即。 e.使用 AES-256。因此,16 字节的密钥 $key 用 0 值填充并扩展到 32 字节的大小。

mcrypt 隐式使用零填充进行加密,在解密过程中不会隐式 删除。不支持 PKCS7 填充。 openssl 隐式应用 PKCS7 填充进行加密,在解密过程中被隐式删除。不支持零填充。如果openssl代码要用Zero padding或者mcyrpt代码要PKCS7 padding,这个要自己实现。

关于从mcrypt迁移到openssl,修改了openssl代码在下文中与 mcrypt 代码在功能上相同,即使用 AES-128 和零填充。对于 AES 变体,只有规范 AES-256-CBC 必须更改为 AES-128-CBC。关于填充,必须使用 OPENSSL_ZERO_PADDING 禁用默认的 PKCS7 填充并且必须实现零填充本身(请注意 OPENSSL_ZERO_PADDING 标志仅禁用填充,但不启用零填充;名称选择错误) :

<?php
$str    = "test";
$key    = 'o6xSYYAVl2eapPI2';
$iv     = 'fedcba9876543210';

function encrypt_openssl($str = NULL, $key, $iv) {

    $encrypted = openssl_encrypt(
        zeroPad($str, 16),                         // Zero pad plaintext
        'AES-128-CBC',                             // Choose AES-128 
        $key, 
        OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING,   // Disable PKCS7 Padding
        $iv); 
     
    return bin2hex(@$encrypted);
}

function zeroPad($text, $bs) {
    $pad = $bs - strlen($text) % $bs;
    return ($pad < 16) ? $text .  str_repeat("[=10=]", $pad) : $text;
}

echo 'Openssl:'.encrypt_openssl($str,$key,$iv); // Openssl:57c86f3089535b3acfbe65cecbb662b9

为了与您的结果进行比较,请注意您混淆了输出的标签,即 openssl 结果被标记为 Mcrypt,反之亦然!

最后一点:一般来说,PKCS7 填充比零填充更可靠,因为前者包含填充长度的信息。零填充不是这种情况,因此在删除填充时(即解密后)无法区分常规字节和填充字节。还有不同的零填充变体,例如如果明文的长度已经对应于块大小的整数倍(此变体使用 mcrypt),则一个不填充,在这种情况下,另一个用完整的块填充。