使用Blowfish和ECB将mcrypt迁移到OpenSSL

时间:2018-09-27 13:16:11

标签: php encryption openssl mcrypt

我一辈子都想不通如何将旧的mcrypt代码迁移到OpenSSL。我在CBC的Blowfish和CBC的Rijndael都可以使用它,但是在ECB的Blowfish却让我难以理解。

是的,我读了Moving from mcrypt with Blowfish & ECB to OpenSSL,并尝试对数据进行零填充,而不是对数据进行零填充,对密钥进行零填充,对密钥进行循环以及它们的任何组合,但似乎没有任何效果。

这是我的代码:

<?php
function encrypt_with_mcrypt($data, $key) {
        return mcrypt_encrypt(MCRYPT_BLOWFISH, $key, $data, MCRYPT_MODE_ECB);
}

function encrypt_with_openssl($data, $key) {
        return openssl_encrypt($data, 'BF-ECB', $key, OPENSSL_RAW_DATA | OPENSSL_DONT_ZERO_PAD_KEY);
}

$data = 'foobar';
$key = 'supersecretkey';

var_dump(base64_encode(encrypt_with_mcrypt($data, $key)));
var_dump(base64_encode(encrypt_with_openssl($data, $key)));

这是输出:

test.php:13:
string(12) "5z0q3xNnokw="
test.php:14:
string(12) "1zyqavq7sCk="

2 个答案:

答案 0 :(得分:3)

mcrypt库/包装器默认为零字节填充(仅在需要时),而OpenSSL库/包装器默认为PKCS#5填充。这意味着单个块的填充方式不同,因此将显示不同的密文块。


一个常见的技巧是解密生成的密文而没有进行任何填充,然后通过查看十六进制的明文+填充检查填充字节。

这将向您显示

5z0q3xNnokw=
666f6f6261720000

用于mcrypt和

1zyqavq7sCk=
666f6f6261720202

用于OpenSSL。

使用较大的纯文本消息,需要对多个块进行加密,这也会向您显示,对于最后一个块,除 之外,加密进行得很好。


首先且仅当mcrypt输入不是8字节的倍数(Blowfish的块大小)时,才对数据进行零填充,然后使用OPENSSL_ZERO_PADDING作为填充模式。

请注意,查看源代码会发现OPENSSL_ZERO_PADDING出于某种未指定的原因似乎意味着包装程序“无填充”,而OPENSSL_NO_PADDING似乎与其他设置冲突-我认为这是PHP OpenSSL包装器API的开发人员犯下的非常糟糕的设计和实现错误。

the great research performed by Reinier可以找到更多信息,该信息显示了API填充/未填充(或忘记填充/未填充,取决于您站立的位置)的方式。

答案 1 :(得分:2)

除了Maarten's answer之外,我没有什么要补充的,除了我认为可以显示一些说明他的单词的代码是很好的。

mcrypt添加零以将明文填充到8字节BF块大小的倍数中,这可以通过打印明文和解密密文的十六进制转储来显示:

$key = "supersecretkey";
$data = "foobar";
$ctxt = mcrypt_encrypt(MCRYPT_BLOWFISH, $key, $data, MCRYPT_MODE_ECB);
$ptxt = mcrypt_decrypt(MCRYPT_BLOWFISH, $key, $ctxt, MCRYPT_MODE_ECB);
echo bin2hex($data).PHP_EOL;
echo bin2hex($ptxt).PHP_EOL;

给出以下十六进制转储:

666f6f626172
666f6f6261720000

openssl默认使用PKCS#5填充,在这种情况下,在块的末尾添加2个字节,值2。

$key = "supersecretkey";
$data = "foobar";
$opts = OPENSSL_RAW_DATA | OPENSSL_DONT_ZERO_PAD_KEY;
$ctxt = openssl_encrypt($data, 'BF-ECB', $key, $opts);
$ptxt = mcrypt_decrypt(MCRYPT_BLOWFISH, $key, $ctxt, MCRYPT_MODE_ECB);
echo bin2hex($data).PHP_EOL;
echo bin2hex($ptxt).PHP_EOL;

给予

666f6f626172
666f6f6261720202

可以通过手动添加填充字节来使mcryptopenssl的密文保持一致。请注意OPENSSL_ZERO_PADDING选项和"\0\0"的附加内容:

$key = "supersecretkey";
$data = "foobar";
$ctxt_mc = mcrypt_encrypt(MCRYPT_BLOWFISH, $key, $data, MCRYPT_MODE_ECB);
$opts = OPENSSL_RAW_DATA | OPENSSL_DONT_ZERO_PAD_KEY | OPENSSL_ZERO_PADDING;
$ctxt_os = openssl_encrypt($data."\0\0", 'BF-ECB', $key, $opts);
echo bin2hex($ctxt_mc).PHP_EOL;
echo bin2hex($ctxt_os).PHP_EOL;

给予:

e73d2adf1367a24c
e73d2adf1367a24c

或者,在使用mcrypt时,在末尾手动插入PKCS#5填充字节:

$key = "supersecretkey";
$data = "foobar";
$ctxt_mc = mcrypt_encrypt(MCRYPT_BLOWFISH, $key, $data."\2\2", MCRYPT_MODE_ECB);
$opts = OPENSSL_RAW_DATA | OPENSSL_DONT_ZERO_PAD_KEY;
$ctxt_os = openssl_encrypt($data, 'BF-ECB', $key, $opts);
echo bin2hex($ctxt_mc).PHP_EOL;
echo bin2hex($ctxt_os).PHP_EOL;

给予

d73caa6afabbb029
d73caa6afabbb029

最后,尝试在禁用填充且长度不等于块大小的倍数的情况下调用openssl_encrypt()

$key = "supersecretkey";
$data = "foobar";
$opts = OPENSSL_RAW_DATA | OPENSSL_DONT_ZERO_PAD_KEY | OPENSSL_ZERO_PADDING;
$ctxt = openssl_encrypt($data, 'BF-ECB', $key, $opts);
echo(openssl_error_string().PHP_EOL)

给予

error:0607F08A:digital envelope routines:EVP_EncryptFinal_ex:data not multiple of block length

备注:名称OPENSSL_ZERO_PADDING令人困惑,但这表示“无填充”。您可能会想使用标志OPENSSL_NO_PADDING,但是该标志不打算与openssl_encrypt()一起使用。其值为3,与OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING相同。相反,it is intended for use with asymmetric cryptography