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

Moving from mcrypt with Blowfish & ECB to OpenSSL

在(不太遥远的)过去,(由长期在这里工作的人)做出了一个决定,总是'encrypt' 数据库 ID 到其他东西,在运行中,无论何时需要外部通信.

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

要将所有当前加密的值保存在 1400 个数据库的 60 个表中,是一项艰巨的任务。有没有办法利用 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:�_���。谁能对此有所启发 - 甚至可能吗?

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

# 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("[=10=]", $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, "[=10=]");
}


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'

问题:

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

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

  3. 当您将 zero-padding 与 openssl 一起使用时(保持与 mcrypt 相同的输出),您应该自己进行填充。好吧,我做了一个技巧来获取 paddingLen,但这真的很简单:只需考虑我们应该追加多少字节才能使总长度为 0/8/16/24/32/40 等