PHP mcrypt to openssl BF-CBC:如何获得相同的加密值

Posted

技术标签:

【中文标题】PHP mcrypt to openssl BF-CBC:如何获得相同的加密值【英文标题】:PHP mcrypt to openssl BF-CBC: how to get the same encrypted value 【发布时间】:2019-10-03 12:37:50 【问题描述】:

需要将使用 mcrypt blowfish cbc 模式的现有加密和解密函数替换为与其等效的 openssl。

它需要能够处理旧值,因此方法需要保持兼容。

让解密工作,加密“几乎”没问题,但还不够。

这是我的代码:

$value = "myTextValue";
$key = 'c40f5b7ad3b7c787d400e923e461064b141fa878ce61cb0d1782593a5a2d842832c80fc2';

$enc = @encrypt_openssl($value, $key);
//$enc = @encrypt_mcrypt($value, $key);
$original_openssl = @decrypt_openssl($enc, $key);
$original_mcrypt = @decrypt_mcrypt($enc, $key);

echo $original_mcrypt."\n";
echo $original_openssl."\n";

function encrypt_openssl($string, $key) 
    $iv_size = openssl_cipher_iv_length("BF-CBC");
    $iv = openssl_random_pseudo_bytes($iv_size);
    $enc = openssl_encrypt($string, "BF-CBC", pack('H*',$key), OPENSSL_RAW_DATA, $iv);
    return base64_encode($iv.$enc);


function encrypt_mcrypt($string, $key) 
    $iv_size = mcrypt_get_iv_size(MCRYPT_BLOWFISH, MCRYPT_MODE_CBC);
    $iv = mcrypt_create_iv($iv_size, MCRYPT_RAND);
    $enc = mcrypt_encrypt(MCRYPT_BLOWFISH, pack('H*', $key), $string, MCRYPT_MODE_CBC, $iv);
    return base64_encode($iv.$enc);


function decrypt_openssl($enc, $key) 
    $iv_size = openssl_cipher_iv_length("BF-CBC");
    $dec = base64_decode($enc);
    $iv = substr($dec, 0, $iv_size);
    $string = openssl_decrypt(substr($dec, $iv_size), "BF-CBC", pack('H*',$key), OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING | OPENSSL_DONT_ZERO_PAD_KEY, $iv);
    return rtrim($string, "\x00");


function decrypt_mcrypt($enc, $key) 
    $iv_size = mcrypt_get_iv_size(MCRYPT_BLOWFISH, MCRYPT_MODE_CBC);
    $dec = base64_decode($enc);
    $iv = substr($dec, 0, $iv_size);
    $string = mcrypt_decrypt(MCRYPT_BLOWFISH, pack('H*', $key), substr($dec, $iv_size), MCRYPT_MODE_CBC, $iv);
    return rtrim($string, "\x00");

使用 openssl 加密时,会添加一些额外的二进制数据。

不是加密专家,只得到了一半,这是我在其他 *** 帖子和全能谷歌的帮助下得到的


编辑

按照 Topaco 的建议,我得到了以下现在可以使用的代码:

function encrypt_openssl($string, $key) 
    $string_padded = $string;
    if (strlen($string_padded) % 8) 
        $string_padded = str_pad($string_padded,
            strlen($string_padded) + 8 - strlen($string_padded) % 8, "\0");
    
    $iv_size = openssl_cipher_iv_length("BF-CBC");
    $iv = openssl_random_pseudo_bytes($iv_size);
    $enc = openssl_encrypt($string_padded, "BF-CBC", pack('H*',$key), OPENSSL_RAW_DATA | OPENSSL_NO_PADDING, $iv);
    return base64_encode($iv.$enc);

【问题讨论】:

【参考方案1】:

openssl_encrypt/decrypt默认使用PKCS7-padding,mcrypt_encrypt/decrypt使用Zero-Byte-padding。

解密后观察到的额外数据是 PKCS7-padding 的填充字节:当前代码使用 PKCS7-padding 进行 openssl_encrypt-call(OPENSSL_ZERO_PADDING-flag 未设置)。由于openssl_decrypt-call(OPENSSL_ZERO_PADDING-标志集)没有使用填充,因此解密后填充仍然存在。请注意,OPENSSL_ZERO_PADDING-标志禁用填充,它表示零字节填充。

虽然 PKCS7-padding 通常比 Zero-Byte-padding 是更好的选择(因为后者不可靠),但在这种情况下使用旧数据的填充更有意义,即 Zero-Byte-padding,关于旧数据的兼容性。否则会有不同填充的数据,通常不能从数据中导出,例如填充的最终块41 42 43 44 45 46 02 02 可能是由 PKCS7 填充或零字节填充创建的:

before padding             after padding
41 42 43 44 45 46 __ __ -> 41 42 43 44 45 46 02 02  PKCS7-Padding
41 42 43 44 45 46 02 02 -> 41 42 43 44 45 46 02 02  Zero-Byte-Padding (variant of mcrypt_encrypt)  

这会使取消填充更加复杂。使用零字节填充可以避免这个问题。

由于openssl_encrypt/decrypt 不支持零字节填充,因此必须显式实现。使用mcrypt_encrypt 的零字节填充变体是有意义的:如果明文已经可以被块大小整除(Blowfish 为 8 字节),则不会添加额外的零字节块。否则,用零字节填充,直到明文的长度对应于块大小的整数倍。

零字节填充必须在openssl_encrypt-调用之前发生。此外,openssl_encrypt-call 本身的填充必须被禁用(设置OPENSSL_ZERO_PADDING-flag,类似于openssl_decrypt-call)。

【讨论】:

很好的解释 - 通过手动添加填充更新了代码

以上是关于PHP mcrypt to openssl BF-CBC:如何获得相同的加密值的主要内容,如果未能解决你的问题,请参考以下文章

将 Mcrypt RC4 转换为 PHP-OpenSSL

php升级到7.+之后openssl替代mcrypt实现解密

PHP 将 mcrypt 转换为 openssl

PHP 使用 openssl 解密数据(使用 mcrypt 加密)

在 PHP 中将 mcrypt_encrypt 转换为 openssl_encrypt

用 OpenSSL 替换 Mcrypt