使用 Blowfish 和 ECB 将 mcrypt 迁移到 OpenSSL

Migrating mcrypt with Blowfish and ECB to OpenSSL

我一辈子都想不出如何将遗留的 mcrypt 代码迁移到 OpenSSL。我用 CBC 为 Blowfish 和 CBC 为 Rijndael 工作,但是 Blowfish 和 ECB 正在躲避我。

是的,我阅读了 并尝试对数据进行零填充,而不是对数据进行零填充,对密钥进行零填充,循环遍历密钥以及它们的任意组合,但似乎没有任何效果工作。

这是我的代码:

<?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="

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


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

这会告诉你:

5z0q3xNnokw=
666f6f6261720000

对于 mcrypt 和

1zyqavq7sCk=
666f6f6261720202

用于 OpenSSL。

使用需要对多个块进行加密的更大的明文消息也会向您表明加密进行得很好除了最后一个块


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

请注意,查看源代码表明 OPENSSL_ZERO_PADDING 由于某些未指明的原因似乎意味着包装器的 "no padding" 并且 OPENSSL_NO_PADDING 似乎与其他设置冲突 - 这我PHP OpenSSL 包装器 API.

的开发人员认为这是一个相当糟糕的设计和实现错误

可以通过 找到更多信息,其中显示了 API 填充/取消填充(或忘记填充/取消填充,具体取决于您所处的位置)。

我对 没有太多要补充的,只是我认为展示一些说明他的话的代码会很好。

mcrypt 添加零以将明文填充为 8 字节 BF 块大小的倍数,这可以通过打印明文和解密密文的 hexdump 来显示:

$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 选项和添加的 "[=25=][=25=]":

$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."[=14=][=14=]", '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."", 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容易混淆,但意思是"no padding"。您可能想使用标志 OPENSSL_NO_PADDING, but that one is not intended to be used with openssl_encrypt(). Its value is 3, which is the same as OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING. In stead, it is intended for use with asymmetric cryptography.