Часть 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 изящно обрабатывать последний блок.

Author: Community, 2009-12-08

2 answers

Проблема в том, что mcrypt не заполняет последний блок, в то время как привязка OpenSSL Ruby использует метод заполнения OpenSSL по умолчанию, который является заполнением PKCS. Я действительно не могу улучшить описание из документации OpenSSL:

Заполнение PKCS работает путем добавления n байтов заполнения значения 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.

 15
Author: caf, 2009-12-10 10:09:31

Похоже, что 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?"
 0
Author: Chloe, 2013-07-26 20:06:20