Часть II:Как заставить Ruby AES-256-CBC и PHP MCRYPT_RIJNDAEL_128 хорошо работать вместе
Вопрос
Этот вопрос является продолжением моего предыдущего, касающегося Как заставить Ruby AES-256-CBC и PHP MCRYPT_RIJNDAEL_128 хорошо работать вместе.Сейчас у меня это работает, но я все еще пытаюсь пойти в другом направлении.Криптограмма, сгенерированная PHP, содержит всю предоставленную информацию, но я не могу заставить код Ruby расшифровать ее без ошибок.
Вот PHP-код, который я использую для генерации криптограммы:
$cleartext = "Who's the clever boy?";
$key = base64_decode("6sEwMG/aKdBk5Fa2rR6vVw==\n");
$iv = base64_decode("vCkaypm5tPmtP3TF7aWrug==");
$cryptogram = mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $key, $cleartext, MCRYPT_MODE_CBC, $iv);
$result = base64_encode($cryptogram);
print "\n'$result'\n";
RESULT
'JM0OxMINPTnF1vwXdI3XdKI0KlVx210CvpJllFja+GM='
Тогда вот попытка расшифровки в Ruby:
>> cipher = OpenSSL::Cipher::Cipher.new('aes-128-cbc')
>> cipher.key = Base64.decode64("6sEwMG/aKdBk5Fa2rR6vVw==\n")
>> cipher.iv = Base64.decode64("vCkaypm5tPmtP3TF7aWrug==")
>> cryptogram = Base64.decode64('JM0OxMINPTnF1vwXdI3XdKI0KlVx210CvpJllFja+GM=')
>> cleartext = cipher.update(cryptogram)
=> "Who's the clever"
>> cleartext << cipher.final
OpenSSL::Cipher::CipherError: bad decrypt
from (irb):100:in `final'
from (irb):100
Что действительно расстраивает в этом, так это то, что из этой зашифрованной строки можно получить весь открытый текст.Повторяем вышеописанное, но добавляем к криптограмме бессмысленный блокнот:
>> cleartext = cipher.update(cryptogram + 'pad')
=> "Who's the clever boy?\000\000\000\000\000\000\000\000\000\000\000"
>> cleartext << cipher.final
OpenSSL::Cipher::CipherError: bad decrypt
from (irb):119:in `final'
from (irb):119
В моем реальном случае использования открытый текст структурирован (строка JSON, если вы спрашиваете), поэтому я чувствую себя комфортно, поскольку могу сказать, что использую эту схему и обнаруживаю плохо зашифрованный ввод, не выполняя cipher.final
.Однако я терпеть не могу такого рода заморочки в своем коде, поэтому мне хотелось бы понять, как заставить код Ruby корректно обрабатывать последний блок.
Решение
Проблема в том, что mcrypt
не дополняет последний блок, тогда как привязка OpenSSL Ruby использует метод заполнения OpenSSL по умолчанию, то есть заполнение PKCS.Я не могу улучшить описание из документации OpenSSL:
PKCS Padding работает, добавляя N Badding Byts nure n, чтобы сделать общую длину данных кратным размером блока.Всегда добавляется накладка, поэтому, если данные уже разногливы размера блока N будут равны размеру блока.Например, если размер блока составляет 8 и 11 байт, должен быть зашифрован, тогда будут добавлены 5 байтов заполнения значения 5.
Перед шифрованием вам нужно будет вручную добавить правильное дополнение в конец открытого текста в PHP.Для этого передайте свой $cleartext
сквозь это pkcs5_pad
функцию на стороне PHP перед ее шифрованием (передавая 16
как размер блока).
function pkcs5_pad ($text, $blocksize)
{
$pad = $blocksize - (strlen($text) % $blocksize);
return $text . str_repeat(chr($pad), $pad);
}
Если вы пойдете другим путем (зашифруете в Ruby и расшифруете с помощью mcrypt), вам придется удалить байты заполнения после расшифровки.
Примечание: Причина, по которой вам нужно добавлять дополнение, даже если открытый текст уже кратен размеру блока (целому блоку заполнения), заключается в том, что при расшифровке вы знаете, что последний байт последнего блока равен всегда количество добавленных дополнений.В противном случае вы не смогли бы отличить открытый текст с одним байтом заполнения от открытого текста без байтов заполнения, который просто закончился значением. 0x01
.
Другие советы
Кажется, PHP \0
дополняет открытый текст перед его шифрованием.Вы можете настроить Ruby на отключение заполнения.
http://www.ruby-doc.org/stdlib-1.9.3/libdoc/openssl/rdoc/OpenSSL/Cipher.html#method-i-padding-3D
Это сработает, но тогда вам придется удалить прокладку вручную.
1.9.3p125 :008 > cipher = OpenSSL::Cipher::Cipher.new('aes-128-cbc')
=> #<OpenSSL::Cipher::Cipher:0x0000000561ee78>
1.9.3p125 :009 > cipher.decrypt
=> #<OpenSSL::Cipher::Cipher:0x0000000561ee78>
1.9.3p125 :010 > cipher.padding = 0
=> 0
1.9.3p125 :011 > cipher.key = Base64.decode64("6sEwMG/aKdBk5Fa2rR6vVw==\n")
=> "\xEA\xC100o\xDA)\xD0d\xE4V\xB6\xAD\x1E\xAFW"
1.9.3p125 :012 > cipher.iv = Base64.decode64("vCkaypm5tPmtP3TF7aWrug==")
=> "\xBC)\x1A\xCA\x99\xB9\xB4\xF9\xAD?t\xC5\xED\xA5\xAB\xBA"
1.9.3p125 :013 > cryptogram = Base64.decode64('JM0OxMINPTnF1vwXdI3XdI2j8NJ8kr+Du0fnkxorNl0=')
=> "$\xCD\x0E\xC4\xC2\r=9\xC5\xD6\xFC\x17t\x8D\xD7t\x8D\xA3\xF0\xD2|\x92\xBF\x83\xBBG\xE7\x93\x1A+6]"
1.9.3p125 :014 > cleartext = cipher.update(cryptogram)
=> "Who's the clever girl?\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
1.9.3p125 :015 > cleartext << cipher.final
=> "Who's the clever girl?\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
1.9.3p125 :042 > cleartext.strip
=> "Who's the clever girl?"