10

I'm generating data to send from a Ruby stack to a PHP stack. I'm using the OpenSSL::Cipher library on the Ruby side and the 'mcrypt' library in PHP. When I encrypt using 'aes-256-cbc' (256-bit block size) in Ruby I need to use MCRYPT_RIJNDAEL_128 (128-bit block size) in PHP to decrypt it. I suspect the Ruby code that is broken, because the cipher.iv_len is 16; I believe it should be 32:

>> cipher = OpenSSL::Cipher::Cipher.new('aes-128-cbc')
=> #<OpenSSL::Cipher::Cipher:0x3067c5c>
>> cipher.key_len
=> 16
>> cipher.iv_len
=> 16
>> cipher = OpenSSL::Cipher::Cipher.new('aes-256-cbc')
=> #<OpenSSL::Cipher::Cipher:0x306de18>
>> cipher.key_len
=> 32
>> cipher.iv_len
=> 16

So here's my test. On the Ruby side, first I generate the key and iv:

>> cipher = OpenSSL::Cipher::Cipher.new('aes-256-cbc')
>> cipher.encrypt
>> iv = cipher.random_iv
>> iv64 = [iv].pack("m").strip
=> "vCkaypm5tPmtP3TF7aWrug=="
>> key = cipher.random_key
>> key64 = [key].pack("m").strip
=> "RIvFgoi9xZaHS/0Bp0J9WDRyND6Z7jrd3btiAfcQ8Y0="

Then I use those keys to do the encryption:

>> plain_data = "Hi, Don, this is a string."
>> cipher = OpenSSL::Cipher::Cipher.new('aes-256-cbc')
>> cipher.encrypt
>> cipher.key = Base64.decode64(key64)
>> cipher.iv = Base64.decode64(iv64)
>> encrypted_data = cipher.update(plain_data)
>> encrypted_data << cipher.final
>> crypt64 = [encrypted_data].pack("m").strip
=> "5gfC/kJcnAV2fJI0haxnLcdraIKWgtu54UoznVxf8K0="

Here's the PHP decryption:

$ruby_crypt = "5gfC/kJcnAV2fJI0haxnLcdraIKWgtu54UoznVxf8K0=";
$encrypted_data = base64_decode($ruby_crypt);
$key = base64_decode("RIvFgoi9xZaHS/0Bp0J9WDRyND6Z7jrd3btiAfcQ8Y0=");
$iv = base64_decode("vCkaypm5tPmtP3TF7aWrug==");
$result = mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $key, $encrypted_data, MCRYPT_MODE_CBC, $iv);
$unencrypt = rtrim($result, "\x00..\x1F");
print "\nUnencrypted token:\n'$unencrypt'\n";

RESULT:
Unencrypted token:
'Hi, Don, this is a string.'

I'd prefer to use the longer block size. Clearly I'm misunderstanding the APIs. Help?

dondo
  • 1,208
  • 1
  • 12
  • 15
  • Tangential to your question, but be careful with AES-256: http://www.schneier.com/blog/archives/2009/07/another_new_aes.html – Adam Lassek Dec 07 '09 at 20:37
  • Thanks, Adam. Perhaps it's tangential, but the point you're tacitly making ("Just use AES-128, it's stronger anyway") is not unimportant. – dondo Dec 07 '09 at 21:46

4 Answers4

13

I wrote an example that somebody else may find explanatory of the discussion above:

$ cat publisher.rb

#!/usr/bin/env ruby

require 'openssl'
require 'base64'

key = '7fc4d85e2e4193b842bb0541de51a497'

cipher = OpenSSL::Cipher::Cipher.new('aes-128-cbc')
cipher.encrypt()
iv = cipher.random_iv

cipher = OpenSSL::Cipher::Cipher.new('aes-256-cbc')
cipher.encrypt()
cipher.key = key
cipher.iv = iv
crypt = cipher.update('This is my text')
crypt << cipher.final()

puts [Base64.encode64(crypt).strip(), Base64.encode64(iv).strip()].join('|')

$ cat consumer.php

$key256 = '7fc4d85e2e4193b842bb0541de51a497';

$fd = fopen("php://stdin", "r");
$tokens = '';
while (!feof($fd))
  $tokens .= fread($fd, 1024);
fclose($fd);

$tokens = explode('|', trim($tokens));
$crypt = $tokens[0];
$iv = $tokens[1];

$crypttext = base64_decode($crypt);
$iv = base64_decode($iv);
$decrypted = mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $key256, $crypttext, MCRYPT_MODE_CBC, $iv);

print $decrypted ."\n";

To test it, from command line try:

$ ruby publisher.rb | php consumer.php

This is my text

Community
  • 1
  • 1
Marco Lazzeri
  • 1,808
  • 19
  • 15
7

I don't know PHP, but reading through related questions on the sidebar, I see Converting Ruby AES256 decrypt function to PHP. This includes a reference to this page, pointing out that the 128 in MCRYPT_RIJNDAEL_128 refers to the block size of the encryption, not the key size. You'll notice that the key size that you've passed between ruby and PHP is 256 bits in both cases. In other words, this seems to be the expected behavior, and you are using the larger key already.

#!/usr/bin/ruby
require 'base64'

puts((Base64.decode64("RIvFgoi9xZaHS/0Bp0J9WDRyND6Z7jrd3btiAfcQ8Y0=").length * 8).to_s)

HTH

Community
  • 1
  • 1
Aidan Cully
  • 5,457
  • 24
  • 27
  • Aha! Thanks, Aiden. I had already read the "Converting Ruby ... to PHP" article above, but missed the link to the second article which is exactly what I needed. Reading that shows that I was, in fact, misunderstanding the APIs; AES block size is always 16-byte. Then the the "128" and "256" in "aes-128-cbc" and "aes-256-cbc" refer to the key size, and the block size is always 128 (16-byte), Which explains the behavior completely. Thanks again. – dondo Dec 07 '09 at 21:51
  • ...sorry, and I should also have noted that conversely the "128" and "256" in the PHP MCRYPT_RIJNDAEL_128 and MCRYPT_RIJNDAEL_256 refer to the block size, the key size is computed from the key that is provided. – dondo Dec 07 '09 at 21:57
  • 2
    So in summary for those playing along at home - Rijndael has both variable block size and an independently variable key size. AES is a subset of Rijndael, with a fixed block size (128 bits) and a choice of two key sizes (128 bits and 256 bits). – caf Dec 07 '09 at 23:42
1

Let me show you some code.

PHP code:

$privateKey = "1234567890123456"; # the size is 16.
$data = "hello";
$iv = "0123456789012345";

$result = mcrypt_encrypt(
  MCRYPT_RIJNDAEL_128, $privateKey, $data, MCRYPT_MODE_CBC, $iv
)

$base64str = base64_encode($result);
$base64str = str_replace("+", "-",  $base64str);
$base64str = str_replace("/","_",  $base64str);

# => f-WffBXnf122NcVBUZ6Rlg==

Ruby code:

require 'base64'
require 'openssl'

private_key = "1234567890123456"
data = "hello"
iv = "0123456789012345"

cipher = OpenSSL::Cipher::AES.new(128, :CBC) 
cipher.encrypt

cipher.padding = 0 # we must disable padding in ruby.
cipher.key = private_key
cipher.iv = iv
block_size = cipher.block_size

# Add padding by yourself.
data = data + "\0" * (block_size - data.bytesize % block_size)
result = cipher.update(data) + cipher.final

Base64.urlsafe_encode64(result)
# ==> f-WffBXnf122NcVBUZ6Rlg==

As you can see I am using AES-128 in ruby because the size of private_key is 16. So you have to use AES-256 if the size of your private_key is 32.

Formula: size_of_private_key * 8.

LcpMarvel
  • 203
  • 2
  • 8
0

I had troubles because the PHP was using a password smaller than 8 characters. In this case one needs to add the 0, to make it compatible with PHP:

mcrypt-encrypt manual page "key

The key with which the data will be encrypted. If it's smaller than the required keysize, it is padded with '\0'. It is better not to use ASCII strings for keys. http://php.net/manual/en/function.mcrypt-encrypt.php It is recommended to use the mhash functions to create a key from a string."

require 'openssl'
cipher = OpenSSL::Cipher.new('DES-ECB')
cipher.encrypt
key =  'passwrd'[0...7].ljust(8, 0.chr)  #Pad the key smaller than 8 chars
cipher.key = key
encrypted = cipher.update('33')
encrypted << cipher.final
dec = Base64.encode64(encrypted).strip()
Nuno
  • 133
  • 7