从使用 Blowfish 和 ECB 的 mcrypt 迁移到 OpenSSL

Posted

技术标签:

【中文标题】从使用 Blowfish 和 ECB 的 mcrypt 迁移到 OpenSSL【英文标题】:Moving from mcrypt with Blowfish & ECB to OpenSSL 【发布时间】:2018-08-16 16:57:55 【问题描述】:

在(不太遥远的)过去,(由在此工作较长时间的人)做出了一个决定,即在需要外部通信时始终将数据库 ID 动态“加密”到其他东西。

现在,我们的主应用程序已从 php 5.x 迁移到 PHP 7.0,分散在基础设施中的微服务运行 7.0 或 7.1。 7.1 服务器不断对 mcrypt 内容发出弃用警告。没什么大不了的,只是现在。但是随着 PHP 7.2 的临近,我们希望不断更新和升级。 Mcrypt 正在阻塞。

将所有当前加密的值保存在 60 个表中,跨越 1400 个数据库,是一项艰巨的任务。有没有办法利用 OpenSSL、Blowfish 和 ECB 来获得相同的编码和解码值,让我们陷入一种虚假的安全感?所有这些都是为了让我们可以提前计划我们的数据库迁移。

基本上,当前加密的值是这样的:

item:13fb7533bf19399ff114468b194ebfaf

这是 ID 123。它通过以下函数来获取该字符串:

$id   = 123;
$type = 'item';

$serialized = serialize('' . $id); // To make sure always a string gets put in

$ivSize = mcrypt_create_iv_size(MCRYPT_BLOWFISH, MCRYPT_MODE_ECB), MCRYPT_RAND);
$iv     = mcrypt_create_iv($ivSize);

$passCrypt = mcrypt_encrypt(MCRYPT_BLOWFISH, $type, $serialized, MCRYPT_MODE_ECB, $iv);
$encoded   = bin2hex($passCrypt); // `13fb7533bf19399ff114468b194ebfaf`

$encryptedId = $type . ':' . $encoded;

这给出了最终结果item:13fb7533bf19399ff114468b194ebfaf

现在,反过来:

$encryptedId = 'item:13fb7533bf19399ff114468b194ebfaf';

$type = 'item';
$encryptedIdOnly = substr($encryptedId, strlen($type) + 1); // `13fb...`

$decoded   = hex2bin($encryptedIdOnly);
$iv        = mcrypt_create_iv(mcrypt_get_iv_size(MCRYPT_BLOWFISH, MCRYPT_MODE_ECB), MCRYPT_RAND);
$decrypted = mcrypt_decrypt(MCRYPT_BLOWFISH, 'item', $decoded, MCRYPT_MODE_ECB, $iv); // This gives ' `s:3:"123";` '

$unserialized = unserialize($decrypted); // '123'

我已经尝试了几个小时,但我完全被任何加密货币所迷惑(但我想学习!)。我当前的代码是:

$cipher = 'BF-ECB';
//$cipher = 'BF'; (I've tried both, no difference)

$isCtypeXDigit = ctype_xdigit($decipher);
$decoded       = hex2bin($decipher);
$ivLength      = openssl_cipher_iv_length($cipher);
$randomBytes   = openssl_random_pseudo_bytes($ivLength);
$decrypted     = openssl_decrypt($decoded, $cipher, $prefix, OPENSSL_RAW_DATA | OPENSSL_NO_PADDING, $randomBytes);
$unserialized  = unserialize($decrypted);

这给了我一千个东西,都类似于��IY_Lc�d:�_���。任何人都可以对此有所启发 - 甚至可能吗?

【问题讨论】:

【参考方案1】:

这很棘手。你可以直接使用代码。

# cat a.php
<?php
function mcrypt_blowfish_encrypt_hex($key, $str)

    $encrypted = mcrypt_encrypt(MCRYPT_BLOWFISH, $key, $str, MCRYPT_MODE_ECB);
    return bin2hex($encrypted);


function make_openssl_blowfish_key($key)

    if("$key" === '')
        return $key;

    $len = (16+2) * 4;
    while(strlen($key) < $len) 
        $key .= $key;
    
    $key = substr($key, 0, $len);
    return $key;


function openssl_blowfish_encrypt_hex($key, $str)

    $blockSize = 8;
    $len = strlen($str);
    $paddingLen = intval(($len + $blockSize - 1) / $blockSize) * $blockSize - $len;
    $padding = str_repeat("\0", $paddingLen);
    $data = $str . $padding;
    $key = make_openssl_blowfish_key($key);
    $encrypted = openssl_encrypt($data, 'BF-ECB', $key, OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING);
    return bin2hex($encrypted);


function openssl_blowfish_decrypt_hex($key, $hex)

    $key = make_openssl_blowfish_key($key);
    $decrypted = openssl_decrypt(hex2bin($hex), 'BF-ECB', $key, OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING);
    return rtrim($decrypted, "\0");



function test()

    for($i = 1; $i < 32; $i++) 
        for($j = 1; $j < 32; $j++) 
            $key = str_repeat('' . rand(0, 9), $j);
            $str = str_repeat('' . rand(0, 9), $i);

            $encoded_openssl = openssl_blowfish_encrypt_hex($key, $str);
            $decoded_openssl = openssl_blowfish_decrypt_hex($key, $encoded_openssl);
            if($decoded_openssl != $str)
                die("encrypt($key, $str) wrong: $encoded_openssl: decrypt failed\n");


            if(function_exists('mcrypt_encrypt')) 
                $encoded_mcrypt = mcrypt_blowfish_encrypt_hex($key, $str);
                if($encoded_openssl != $encoded_mcrypt)
                    die("encrypt($key, $str) wrong: $encoded_openssl, mcrypt=$encoded_mcrypt\n");
            

            echo "key='$key', str='$str', encrypted='$encoded_openssl'\n";
        
    


echo "openssl: thisismyitemyes:" . openssl_blowfish_encrypt_hex('thisismyitemyes', serialize('6918')) . "\n";
echo "openssl: headphone:" . openssl_blowfish_encrypt_hex('headphone', serialize('581856')) . "\n";

test();

然后运行,它可以工作:

# php a.php
openssl: thisismyitemyes:b192ac0f6105416a710aec3ce92b1085
openssl: headphone:ef057c036eb024865406838c62590a93
key='7', str='3', encrypted='945b638624ecbd5e'
key='22', str='1', encrypted='3daf096bdc744d8a'
key='888', str='0', encrypted='b164bb0b603f439e'
key='2222', str='9', encrypted='d3458df30aef0b4b'
...
...
key='3333333333333333333333333333333', str='11111111111111111111111111111', encrypted='b0c9bf45d6f5c7b3b0c9bf45d6f5c7b3b0c9bf45d6f5c7b363a25777c712f1d5'
key='4444444444444444444444444444444', str='999999999999999999999999999999', encrypted='dd6aaf466121c0f6dd6aaf466121c0f6dd6aaf466121c0f659a2271369ab6731'
key='7777777777777777777777777777777', str='3333333333333333333333333333333', encrypted='6591e9cc92a6473a6591e9cc92a6473a6591e9cc92a6473a208a7a562babc60c'

问题:

    IV 在 ECB 模式下被忽略,因此只需删除代码中的所有 IV。

    由于错误:https://bugs.php.net/bug.php?id=72362。在mcrypt 中,河豚键由短键循环。但是在openssl 中,blowfish 键被短键补零。所以我们需要为openssl制作一个循环密钥来解密mcrypt的加密。

    当您在 openssl 中使用零填充时(保持与 mcrypt 相同的输出),您应该自己进行填充。好吧,我做了一个技巧来获得 paddingLen,但这真的很容易:只需考虑我们应该追加多少字节以使总长度为 0/8/16/24/32/40 等。

【讨论】:

@shawn 我认为你应该只修剪openssl_blowfish_decrypt_hex 中的\0,以免删除空格和0。我认为最后一行应该看起来像return trim($decrypted, "\0");

以上是关于从使用 Blowfish 和 ECB 的 mcrypt 迁移到 OpenSSL的主要内容,如果未能解决你的问题,请参考以下文章

iOS blowfish加密解密

安全模块 - 对称加密算法

将 Blowfish 加密从 Java 转换为 Python

Blowfish 和 Blowfish-compat 有啥区别?

PHP 从 MCRYPT_MODE_ECB 切换到 AES-256-ECB

SHA512 与 Blowfish 和 Bcrypt [关闭]