Миграция 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="
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 заполняет/распаковывает (или забывает заполнять/распаковывать, в зависимости от того, где вы находитесь).
Мне нечего добавить к ответу Мартена, за исключением того, что я подумал, что было бы неплохо показать код, иллюстрирующий его слова.
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
. Вместо этого он предназначен для использования с асимметричной криптографией.