【问题标题】:Migrating mcrypt with Blowfish and ECB to OpenSSL使用 Blowfish 和 ECB 将 mcrypt 迁移到 OpenSSL
【发布时间】:2019-03-03 10:09:04
【问题描述】:

我终其一生都无法弄清楚如何将我的旧 mcrypt 代码迁移到 OpenSSL。我用 CBC 为 Blowfish 工作,用 CBC 为 Rijndael 工作,但欧洲央行的 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="

【问题讨论】:

    标签: php encryption openssl mcrypt


    【解决方案1】:

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


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

    这会告诉你:

    5z0q3xNnokw=
    666f6f6261720000
    

    对于 mcrypt 和

    1zyqavq7sCk=
    666f6f6261720202
    

    对于 OpenSSL。

    使用需要加密多个块的较大纯文本消息也会向您表明,最后一个块的加密进展顺利除了


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

    请注意,查看源代码显示 OPENSSL_ZERO_PADDING 出于某种未指定的原因似乎意味着包装器的“无填充”,而 OPENSSL_NO_PADDING 似乎与其他设置冲突 - 我认为这是一个相当糟糕的设计,并且PHP OpenSSL wrapper API 的开发者的实现错误。

    更多信息可以通过the great research performed by Reinier 找到,它显示了 API 如何填充/取消填充(或忘记填充/取消填充,具体取决于您的立场)。

    【讨论】:

    • 学习一个男人如何钓鱼,你的 kajak 总是被鱼线困住,这个男人会对你大喊大叫:P
    • “使用 OPENSSL_ZERO_PADDING 可能会导致麻烦,因为 OpenSSL 总是填充”:我认为这是不正确的。在这种情况下,OpenSSL 的 PHP 粘合层显式禁用填充 here,在这种情况下,如果明文长度不是 8 个字节的倍数,openssl_encrypt() 调用将失败。
    • 是的,我同意你所说的一切,除了那句话。出于教育目的,我写了一个额外的答案,也简要介绍了该主题,但在其他方面支持您的答案。
    • 谢谢:-)。有一点支持 PHP OpenSSL 包装 API:函数 openssl_encrypt() 的文档提到 OPENSSL_ZERO_PADDING 而不是 OPENSSL_NO_PADDING。同样,openssl_public_encrypt() 提到 OPENSSL_NO_PADDING 而不是 OPENSSL_ZERO_PADDING。但是,这仍然让每个人都感到困惑。
    【解决方案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

    【讨论】:

      猜你喜欢
      • 2018-08-16
      • 2012-01-28
      • 2017-12-26
      • 2018-10-04
      • 2020-01-08
      • 2015-11-16
      • 2016-05-14
      • 1970-01-01
      • 2020-06-23
      相关资源
      最近更新 更多