Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does something encrypted in PHP not match the same string encrypted in Ruby?

Here are my requirements:

I need to encrypt a string in PHP using AES encryption (including a random iv), Base64 encode it, then URL-encode it so that it can be passed as a URL parameter.

I am trying to get the same result in both PHP and Ruby, but I can't make it work.

Here is my PHP code:

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));

Here is my Ruby code:

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)

The infuriating thing is that both code samples produce similar but not identical hashes. For example, the above code generates (base64 encoded, not URL encoded):

PHP: /aRCGgLBMOOAarjjtfTW2Qg2OtbPDLhx3KmgfgMzDJU=

Ruby: /aRCGgLBMOOAarjjtfTW2XIZhZ9VjBx8PdozxSL8IE0=

Can anyone explain what I'm doing wrong here? Also, it is easier for me (since I am a Ruby guy, not PHP usually) to fix the Ruby code rather than the PHP code. So if you wanted to provide a solution in Ruby that would pair well with PHP, I would be most appreciative.

Oh, and also, in production the iv really will be random, but for this example I set it to be permanently the same so that output could be compared.

EDIT:

Thanks to Eugen Rieck's answer, I arrived at a solution. Ruby pads the blocks, but PHP does not, and you have to do it manually. Change the PHP code to the following, and you get encrypted strings that the above Ruby code can decipher easily:

$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);
}
like image 421
John Avatar asked Feb 08 '12 01:02

John


1 Answers

Turns out, this is quite easy: The padding is the culprit.

AES is a block cipher, so it works on fixed-size blocks. This means, that the last block will allways be padded, and , you know, the good thing about standards is, that there are so many to chose from. PHP uses zero padding, you will have to look into AESCrypt to find out what Ruby uses.

The same problem exists with the various JS AES libraries and PHP. We ended up in allways doing our own padding, as this has driven me into blood-red fury quite some times.

Ofcourse this explains, why the first part (carrying the info) is identical, while the second part (carrying the padding) is different.

like image 79
Eugen Rieck Avatar answered Oct 19 '22 14:10

Eugen Rieck