Почему что-то, зашифрованное в PHP, не соответствует той же строке, зашифрованной в Ruby?

Вот мои требования:

Мне нужно зашифровать строку в PHP с использованием шифрования AES (включая случайный iv), закодировать ее в Base64, а затем закодировать в URL, чтобы ее можно было передать в качестве параметра URL.

Я пытаюсь получить одинаковый результат как в PHP, так и в Ruby, но не могу заставить его работать.

Вот мой PHP-код:

function encryptData($data,$iv){
    $cipher = mcrypt_module_open(MCRYPT_RIJNDAEL_128, '', MCRYPT_MODE_CBC, '');
    $iv_size = mcrypt_enc_get_iv_size($cipher);
    if (mcrypt_generic_init($cipher, 'g6zys8dlvvut6b1omxc5w15gnfad3jhb', $iv) != -1){
        $cipherText = mcrypt_generic($cipher,$data );
        mcrypt_generic_deinit($cipher);
        return $cipherText;
    }
    else {
        return false;
    }
}
$data = 'Mary had a little lamb';
$iv = '96b88a5f0b9efb43';
$crypted_base64 = base64_encode(encryptData($data, $iv));

Вот мой код Руби:

module AESCrypt
  def AESCrypt.encrypt(data, key, iv)
    aes = OpenSSL::Cipher::Cipher.new("aes-256-cbc")
    aes.encrypt
    aes.key = key
    aes.iv = iv
    aes.update(data) + aes.final      
  end
end

plaintext = "Mary had a little lamb"
iv = "96b88a5f0b9efb43"
@crypted = AESCrypt::encrypt(plaintext, "g6zys8dlvvut6b1omxc5w15gnfad3jhb", iv)
@crypted_base64 = Base64.encode64(@crypted)
@crypted_base64_url = CGI.escape(@crypted_base64)

Бесит то, что оба примера кода производят похожие, но не идентичные хэши. Например, приведенный выше код генерирует (в кодировке base64, а не в кодировке URL):

PHP: /aRCGgLBMOOAarjjtfTW2Qg2OtbPDLhx3KmgfgMzDJU=

Руби: /aRCGgLBMOOAarjjtfTW2XIZhZ9VjBx8PdozxSL8IE0=

Кто-нибудь может объяснить, что я здесь делаю неправильно? Кроме того, мне легче (поскольку я обычно работаю с Ruby, а не с PHP) исправлять код Ruby, а не код PHP. Поэтому, если вы хотите предоставить решение на Ruby, которое будет хорошо сочетаться с PHP, я буду очень признателен.

Да, а также, в продакшене iv действительно будет случайным, но для этого примера я установил его постоянным, чтобы можно было сравнить вывод.

ИЗМЕНИТЬ:

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

$iv = '96b88a5f0b9efb43';
$data = 'Mary had a little lamb';

function encryptData($data,$iv){
    $key = 'g6zys8dlvvut6b1omxc5w15gnfad3jhb';
    $padded_data = pkcs5_pad($data);
    $cryptogram = mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $key, $padded_data, MCRYPT_MODE_CBC, $iv);
    return $cryptogram;
}

function pkcs5_pad ($text, $blocksize){
    $pad = $blocksize - (strlen($text) % $blocksize);
    return $text . str_repeat(chr($pad), $pad);
}

person John    schedule 08.02.2012    source источник
comment
Не могу помочь с решением, но я думаю, что это может быть как-то связано с заполнением блоков (поскольку выходные строки одинаковы для первых n символов). Возможно, вы захотите попробовать добавить отступы к обычному тексту вручную в зависимости от размера блока.   -  person Mikk    schedule 08.02.2012
comment
Ваш код Ruby вызывает AES-256. Ваш код PHP вызывает AES-128. Вы уверены, что это правильно? Ваша капельница явно рассчитана на 128...   -  person Charles    schedule 08.02.2012
comment
@ Чарльз, я тоже этого не понимаю. Тем не менее, это единственный способ, которым все работает. Переключение PHP на вызов AES-256 приводит к тому, что Ruby выдает ошибку расшифровки из OpenSSL. (См. ветку, указанную в моем комментарии к ответу ниже, это обсуждается далее) Также это дает полное объяснение странностей шифрования PHP. Я думаю, что это проясняет ваш вопрос.   -  person John    schedule 09.02.2012


Ответы (1)


Оказывается, это довольно просто: виновником является отступ.

AES — это блочный шифр, поэтому он работает с блоками фиксированного размера. Это означает, что последний блок всегда будет заполнен, и, знаете, в стандартах хорошо то, что есть из чего выбирать. PHP использует заполнение нулями, вам придется заглянуть в AESCrypt, чтобы узнать, что использует Ruby.

Та же проблема существует с различными библиотеками JS AES и PHP. В конце концов, мы всегда делали свои собственные отступы, так как это несколько раз приводило меня в кроваво-красную ярость.

Конечно, это объясняет, почему первая часть (несущая информацию) идентична, а вторая часть (несущая заполнение) отличается.

person Eugen Rieck    schedule 08.02.2012
comment
Вы имеете в виду, что PHP использует нулевое заполнение (т.е. вообще без заполнения) или что он использует нулевое заполнение (т.е. заполнение последнего блока 000000)? Сегодня я разыскал этот вопрос, который, похоже, указывает на то, что Ruby дополняет блоки, а mcrypt - нет. Я отредактировал свой вопрос выше, чтобы включить мой окончательный ответ. - person John; 09.02.2012
comment
@John Невозможно не использовать заполнение: блочному шифру по определению требуется полный блок данных для работы, поэтому отсутствие заполнения вообще не вариант. PHP заполняет неполный блок значением 0x00, что является одной из, но не единственной возможностью. IIRC OpenSSL использует RFC1423. См., например. chilkatsoft.com/p/p_119.asp для получения дополнительной информации. - person Eugen Rieck; 09.02.2012