Миграция mcrypt с Blowfish и ЕЦБ в OpenSSL


Я ни за что на свете не могу понять, как перенести мой устаревший код mcrypt в OpenSSL. Я заставил его работать для Blowfish с CBC и для Rijndael с CBC, но Blowfish с ECB ускользает от меня.

И да, я прочитал Переход от mcrypt с Blowfish & ECB к 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="
Author: Björn Tantau, 2018-09-27

2 answers

Библиотека/оболочка mcrypt по умолчанию использует заполнение нулевым байтом (только при необходимости), в то время как библиотека/оболочка OpenSSL по умолчанию использует заполнение PKCS#5. Это означает, что один блок заполняется по-разному и, следовательно, будет отображать другой блок зашифрованного текста.


Распространенный трюк состоит в том, чтобы расшифровать полученный зашифрованный текст без распаковки, а затем проверить байты заполнения, просмотрев открытый текст + заполнение в шестнадцатеричных числах.

Это покажет вы:

5z0q3xNnokw=
666f6f6261720000

Для mcrypt и

1zyqavq7sCk=
666f6f6261720202

Для OpenSSL.

Использование сообщения открытого текста большего размера, для которого требуется зашифровать несколько блоков, также показало бы вам, что шифрование идет нормально , за исключением для последнего блока.


Сначала обнулите свои данные тогда и только тогда, когда ввод mcrypt не кратен 8 байтам (размер блока Blowfish), затем используйте OPENSSL_ZERO_PADDING в качестве режима заполнения.

Обратите внимание, что просмотр исходного кода показывает, что OPENSSL_ZERO_PADDING по какой-то неуказанной причине, похоже, означает "без заполнения" для оболочки и OPENSSL_NO_PADDING, похоже, конфликтует с другими настройками - это я расцениваю как довольно плохую ошибку в дизайне и реализации разработчиками API-оболочки PHP OpenSSL.

Дополнительную информацию можно найти в замечательном исследовании, проведенном Рейниром , которое показывает, как API заполняет/распаковывает (или забывает заполнять/распаковывать, в зависимости от того, где вы находитесь).

 3
Author: Maarten Bodewes, 2018-09-27 21:28:31

Мне нечего добавить к ответу Мартена, за исключением того, что я подумал, что было бы неплохо показать код, иллюстрирующий его слова.

mcrypt добавляет нули для заполнения открытого текста размером, кратным размеру блока BF, равному 8 байтам, что может быть показано путем печати шестнадцатеричных дампов как открытого текста, так и расшифрованного зашифрованного текста:

$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

Зашифрованный текст для mcrypt и openssl можно сделать согласованным, вручную добавив байты заполнения. Обратите внимание на параметры 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

В качестве альтернативы, вручную вставляя байты заполнения PKCS#5 в конце при использовании mcrypt:

$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. Вместо этого он предназначен для использования с асимметричной криптографией.

 2
Author: Reinier Torenbeek, 2018-09-28 00:25:04