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);
}
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With