Часть II: Как сделать Ruby AES-256-CBC и PHP MCRYPT_RIJNDAEL_128 хорошо сочетаться

Этот вопрос является продолжением моей последней, касающейся Как сделать Ruby AES-256-CBC и PHP MCRYPT_RIJNDAEL_128 хорошо взаимодействовать. Сейчас у меня это работает, но я все еще стараюсь идти в другую сторону. Сгенерированная PHP криптограмма имеет всю информацию, которая была предоставлена, но я не могу заставить Ruby-код расшифровывать его без ошибок.

Вот код PHP, который я использую для генерации криптограммы:

$cleartext = "Who 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 the clever"
>> cleartext << cipher.final
OpenSSL::Cipher::CipherError: bad decrypt
 from (irb):100:in `final'
 from (irb):100

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

  >> cleartext = cipher.update(cryptogram + 'pad')
  => "Who 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. Тем не менее, я не могу терпеть такого рода kludge в своем коде, поэтому я хотел бы понять, как сделать код ruby ​​обработанным окончательным блоком изящно.

Ответ 1

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

Работа дополнений PKCS путем добавления n дополнений байтов значения n, чтобы сделать общее число длина данных a кратное размеру блока. Прокладка всегда добавляется, если данные уже кратное размеру блока 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), вам придется отключить байты заполнения после дешифрования.

Боковое примечание. Причина, по которой вам нужно добавить отступы, даже если открытый текст уже кратен блокам (целый блок отступов), заключается в том, что при расшифровке вы знаете, что последний байт последнего блока всегда добавляется добавление. В противном случае вы не могли бы разделить различие между cleartext с одним байтом заполнения и с открытым текстом без байтов заполнения, которые только что закончились значением 0x01.

Ответ 2

Похоже, 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 the clever girl?\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
1.9.3p125 :015 > cleartext << cipher.final
 => "Who the clever girl?\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"



1.9.3p125 :042 > cleartext.strip
 => "Who the clever girl?"