4

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

Community
  • 1
  • 1
Thilo
  • 17,565
  • 5
  • 68
  • 84
  • 1
    I'd say your ruby version does not include the randomly generated IV. – Kerrek SB Jul 14 '11 at 21:12
  • AFAIK you don't need to explicitly assign it. In any case, doing that makes no difference to the ruby result. – Thilo Jul 14 '11 at 21:15
  • @Kerrek: btw ecb mode in mcrypt doesnt use IV at all. – fyr Jul 14 '11 at 22:17
  • 1
    Hey, your "salts" are different! – Kerrek SB Jul 14 '11 at 22:38
  • 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 Jul 14 '11 at 22:46
  • Just a typo that slipped in somewhere - fixed now, the output was created from the same salts. Thanks though. – Thilo Jul 15 '11 at 06:35
  • `ECB mode is the only mode that does not require an IV, but there is almost no legitimate use case for this mode because of the fact that it does not sufficiently hide plaintext patterns.` http://www.ruby-doc.org/stdlib-1.9.3/libdoc/openssl/rdoc/OpenSSL/Cipher.html#label-Choosing+an+IV – Chloe Jul 26 '13 at 21:14
  • You cannot do the same encryption in Ruby with the standard OpenSSL library. You are using RIJNDAEL with a 256 bit (32 byte) block size, but AES is always 128 bit (16 byte) block size. – Chloe Jul 26 '13 at 21:34

3 Answers3

5

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.

fyr
  • 20,227
  • 7
  • 37
  • 53
  • That did it, thanks so much. OpenSSL isn't a requirement for me, it's just kind of the ruby default. After getting libmcrypt and the ruby-mcrypt gem installed this works perfectly. – Thilo Jul 15 '11 at 06:45
1

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
bonyiii
  • 2,833
  • 29
  • 25
0

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

Anatoly
  • 15,298
  • 5
  • 53
  • 77
  • Thanks, but I need to replicate an existing PHP encryption in ruby. fyr's answer did the trick. – Thilo Jul 16 '11 at 11:10