4
votes

I'm in the process of rebuilding a PHP web app in Ruby on Rails, and would dearly love to avoid forcing all existing users to reset their encrypted passwords. The PHP site uses mcrypt_encrypt with AES-256-ECB, and I can't for the life of me get the same cipher text using ruby's OpenSSL. I can't decrypt them either (which is good in principle) since what's actually stored in the user DB is an MD5 hash of the AES cipher text.

I've read these previous, closely related questions and the very helpful answers:

including the pages referenced there, and if I understand correctly, the PHP and ruby implementations use different padding methods. Since I have to live with how things work on the PHP side, is there any way to force the same padding method on ruby/OpenSSL somehow? I'm using ruby 1.9.2-p180.

Here's the sample code in PHP:

$salt = "12345678901234567890123456789012";
$plain = "password";
$iv_size = mcrypt_get_iv_size(MCRYPT_RIJNDAEL_256, MCRYPT_MODE_ECB);
$iv = mcrypt_create_iv($iv_size, MCRYPT_RAND);
$cipher = mcrypt_encrypt(MCRYPT_RIJNDAEL_256, $salt, $plain, MCRYPT_MODE_ECB, $iv);

echo md5($cipher);

Output: 6337137fd88148250fd135a43dbeb84a

and in ruby:

require 'openssl'

salt = "12345678901234567890123456789012"
plain = "password";

c = OpenSSL::Cipher.new("AES-256-ECB")
c.encrypt
c.key = salt
cipher = c.update(plain)
cipher << c.final

puts Digest::MD5.hexdigest(cipher)

Output: 18dee36145c07ab83452aefe2590c391

3
I'd say your ruby version does not include the randomly generated IV. - Kerrek SB
AFAIK you don't need to explicitly assign it. In any case, doing that makes no difference to the ruby result. - Thilo
@Kerrek: btw ecb mode in mcrypt doesnt use IV at all. - fyr
Hey, your "salts" are different! - Kerrek SB
Yep they are different in his example but even if he uses the correct "key" in the ruby script openssl will output something different because of the padding described in my post. - fyr

3 Answers

5
votes

Actually not in general an openssl solution but maybe it is ok for you to have a working example.

require 'mcrypt'
require 'openssl'

plaintext = 'password'
puts plaintext

key = '12345678901234567890123456789012'

enc = Mcrypt.new(:rijndael_256, :ecb, key, nil, :zeros)
encrypted = enc.encrypt(plaintext)

puts Digest::MD5.hexdigest(encrypted)

I used an additional gem(ruby-mcrypt). Seems to be an issue with openssl. Actually the issue seems to be that Openssl does not support zero padding and uses either no-padding or default-openssl-padding. Due to the fact that you use zero padding in php you must use zero padding also in ruby.

Output on my machine for the php script:

[~/test] ➔ php5 t.php 
6337137fd88148250fd135a43dbeb84a

and for the ruby script:

[~/test] ➔ ruby t2.rb 
password
6337137fd88148250fd135a43dbeb84a

and my ruby version:

[~/test] ➔ ruby -version
ruby 1.9.2p0 (2010-08-18 revision 29036) [i686-linux]

Hope this helps.

1
votes

if key size is not standard on php side, you need to fill the key with zeros to next valid key size, in order to make ruby side works like this:

php_encrypted = string_encoded_with_php_mcrypt

key = "longerthan16butnot24".to_a.pack('a24')
enc = Mcrypt.new(:rijndael_256, :ecb, key, nil, :zeros)
enc.decrypt(php_encrypted)

In this case next valid key length is 24.

For :rijndael_256 valid key lengths are: 16, 24, 32

You can get more info on algorithms:

Mcrypt.algorithm_info(:rijndael_256
0
votes

if you can use other encrypt methods, you can try TEA Block Encryption. I have adopted the method across Ruby, JS, ActionScript. It should work with PHP as well. github repo is here