How to Make Ruby Aes-256-Cbc and PHP Mcrypt_Rijndael_128 Play Well Together

How to make Ruby AES-256-CBC and PHP MCRYPT_RIJNDAEL_128 play well together

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

Part II: How to make Ruby AES-256-CBC and PHP MCRYPT_RIJNDAEL_128 play well together

The problem is that mcrypt isn't padding the last block, whereas Ruby's OpenSSL binding uses the default OpenSSL padding method, which is PKCS padding. I can't really improve on the description from the OpenSSL documentation:

PKCS padding works by adding n padding
bytes of value n to make the total
length of the data a
multiple of the block size. Padding is
always added so if the data is already
a multiple of the block size n will
equal the block size. For example if
the block size is 8 and 11 bytes are
to be encrypted then 5 padding bytes
of value 5 will be added.

You'll need to manually add proper padding to the end of the cleartext in PHP before encrypting. To do that, pass your $cleartext through this pkcs5_pad function on the PHP side before you encrypt it (passing 16 as the blocksize).

function pkcs5_pad ($text, $blocksize)
{
$pad = $blocksize - (strlen($text) % $blocksize);
return $text . str_repeat(chr($pad), $pad);
}

If you also go the other way (encrypt in Ruby and decrypt with mcrypt), you'll have to strip off the padding bytes after decrypting.

Side note: The reason you have to add padding even if the cleartext is already a multiple of the blocksize (a whole block of padding), is so that when you are decrypting you know that the last byte of the last block is always the amount of padding added. Otherwise, you couldn't tell the difference between cleartext with a single padding byte and a cleartext with no padding bytes that just happened to end in the value 0x01.

Can I replicate the exact behaviour of PHP's AES encryption in ruby?

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.

PHP (mcrypt_encrypt) Ruby (AES-256-CBC) Encryption Different Results

  1. Don't hard code IV's , it is insecure. IVs must be random but can be public , so just use

    mcrypt_create_iv and prepend it to the front of the ciphtertext and then extract it before
    decrypting

  2. You likely have three problems

    1. MCRYPT_RIJNDAEL_256 is nott AES. AES is a specific version RIJNDAEL that was standardized with a 128 bit block size and either 128 or 256 bit keys. MCRYPT_RIJNDAEL_256 is RIJNDAEL with a 256 bit block size. You want to use MCRYPT_RIJNDAEL_128 which is actually AES. For php, key length is just determined by the length of the key. So just give it a 256 bit ( 32 character) key and you will be fine. Note block size does not effect security really, so don't worry about the deference and just use AES with a 256 bit key: its good enough for the NSA and top secret data.
    2. Padding. AES only takes fixed 128 bit chunks, so you must pad out the text to be a multiple of that size. PHP doesn't really bad and i believe SSL uses pkcs7 padding. Note that even with different padding schemes, for most of them the start of the cipher text should the the same there just may be garbage at the end.

    3. String encoding. AES is defined with bit inputs, in c typically this is a byte array. Ruby and PHP use strings. I'd be willing to bet your string encodings are different.

Rijndael AES-128 encryption decryption in Ruby

The problem is that mcrypt isn't padding the last block, whereas Ruby's OpenSSL binding uses the default OpenSSL padding method, which is PKCS padding. I can't really improve on the description from the OpenSSL documentation:

PKCS padding works by adding n padding bytes of value n to make the total length of the data a multiple of the block size. Padding is always added so if the data is already a multiple of the block size n will equal the block size. For example if the block size is 8 and 11 bytes are to be encrypted then 5 padding bytes of value 5 will be added.
You'll need to manually add proper padding to the end of the cleartext in PHP before encrypting. To do that, pass your $cleartext through this pkcs5_pad function on the PHP side before you encrypt it (passing 16 as the blocksize).

function pkcs5_pad ($text, $blocksize)
{
$pad = $blocksize - (strlen($text) % $blocksize);
return $text . str_repeat(chr($pad), $pad);
}

If you also go the other way (encrypt in Ruby and decrypt with mcrypt), you'll have to strip off the padding bytes after decrypting.

Side note: The reason you have to add padding even if the cleartext is already a multiple of the blocksize (a whole block of padding), is so that when you are decrypting you know that the last byte of the last block is always the amount of padding added. Otherwise, you couldn't tell the difference between cleartext with a single padding byte and a cleartext with no padding bytes that just happened to end in the value 0x01.

Migrate PHP AES encryption from mcrypt to openssl return different encrypted string

Both codes use different AES variants and paddings. The mcrypt code applies AES-128 and Zero padding, the openssl code AES-256 and PKCS7 padding. To make sure that both ciphertexts match, both codes must use the same AES variant and padding.

mcrypt identifies the AES variant from the key size. Since $key is a 16 bytes key, AES-128 is used. openssl determines the AES variant based on the specification passed in the 2nd parameter. Keys that are too short are padded with 0 values to the required length, keys that are too long are truncated. Here, AES-256-CBC is specified, i. e. AES-256 is used. The 16 bytes key $key is therefore padded with 0 values and extended to a size of 32 bytes.

mcrypt implicitly uses Zero padding for encryption, which is not implicitly removed during decryption. PKCS7 padding is not supported. openssl implicitly applies PKCS7 padding for encryption, which is implicitly removed during decryption. Zero padding is not supported. If the openssl code should use Zero padding or the mcyrpt code PKCS7 padding, this must be implemented by yourself.

With regard to the migration from mcrypt to openssl, the openssl code is modified in the following to be functionally identical to the mcrypt code, i.e. AES-128 and Zero padding is used. With regard to the AES variant, only the specification AES-256-CBC must be changed to AES-128-CBC. Concerning padding, the default PKCS7 padding must be disabled using OPENSSL_ZERO_PADDING and Zero padding itself must be implemented (note that the OPENSSL_ZERO_PADDING flag only disables padding, but does not enable Zero padding; the name is badly chosen):

<?php
$str = "test";
$key = 'o6xSYYAVl2eapPI2';
$iv = 'fedcba9876543210';

function encrypt_openssl($str = NULL, $key, $iv) {

$encrypted = openssl_encrypt(
zeroPad($str, 16), // Zero pad plaintext
'AES-128-CBC', // Choose AES-128
$key,
OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING, // Disable PKCS7 Padding
$iv);

return bin2hex(@$encrypted);
}

function zeroPad($text, $bs) {
$pad = $bs - strlen($text) % $bs;
return ($pad < 16) ? $text . str_repeat("\0", $pad) : $text;
}

echo 'Openssl:'.encrypt_openssl($str,$key,$iv); // Openssl:57c86f3089535b3acfbe65cecbb662b9

For a comparison with your result, note that you have confused the labels of the outputs, i.e. the openssl result is labeled Mcrypt and vice versa!

A final note: In general, PKCS7 padding is more reliable than Zero padding, as the former contains the information of the padding length. This is not the case with Zero padding, so when removing the padding (i.e. after decryption) it is not possible to distinguish between regular and padding bytes. There are also different Zero padding variants, e.g. one does not pad if the length of the plaintext already corresponds to an integer multiple of the blocksize (this variant uses mcrypt), the other one pads with a complete block in this case.

How to encrypt plaintext with AES-256 CBC in PHP using OpenSSL?

AES-256 (OpenSSL Implementation)

You're in Luck.

The openssl extension has some pretty easy to use methods for AES-256. The steps you need to take are basically...

  1. Generate a 256-bit encryption key (This needs storing somewhere)

    • $encryption_key = openssl_random_pseudo_bytes(32);
  2. Generate an "initialization vector" (This too needs storing for decryption but we can append it to the encrypted data)

    • $iv = openssl_random_pseudo_bytes(openssl_cipher_iv_length('aes-256-cbc'));
  3. encrypt data using openssl_encrypt()
    • openssl_encrypt($data, 'aes-256-cbc', $encryptionKey, $options, $initializationVector)
    • the $options can be set to 0 for default options or changed to OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING
  4. append the initialisation vector to the encrypted data

    • $encrypted = $encrypted . ':' . $iv;
  5. retrieve the encrypted data and the initialization vector.

    • explode(':' , $encrypted);
  6. decrypt data using openssl_decrypt()
    • openssl_decrypt($encryptedData, 'aes-256-cbc', $encryptionKey, $options, $initializationVector)

Enabling openssl

openssl_functions() won't be available by default, you can enable this extension in your php.ini file by uncommenting the line. ;extension=php_openssl.dll by removing the leading ;

PHP - Fiddle.

http://phpfiddle.org/lite/code/9epi-j5v2

Encrypting to AES256 CBC in PHP with a 40 character key

For future reference for anyone else working on this, this is how I got it working:

$CREDENTIALS = 'user:pass';
$SECRET_KEY = 'xxxx'; //40 character secret key

$key256 = @pack("H*" , @hash('sha256', $SECRET_KEY));
$encrypted_string = @base64_encode( openssl_encrypt( $CREDENTIALS, "aes-256-cbc", $key256, true) );

Once that was finished, I was able to add that into the URL a 'credentials' parameter, along with other necessary parameters, generate a signature via base64 sha256, then send the signature w/ the parameters out to the api server.

PHP's mcrypt_encrypt and openssl (AES) output are different

It is most likely the padding. Notice that the first block is the same for each and the last block is different.

The text being encrypted is 28 bytes so the the last block will have 4 bytes of padding: 16-(28%16) = 4.

PHP mcrypt does not support standard PKCS#7 (née PKCS#5) padding, only non-standard null padding.

So PHP mcrypt will appoint 4-bytes of 0x00 and openssl 4-bytes of 0x04. See PKCS#7 padding.

So if you want to create the same encrypted output with openssl_encrypt you need too specify zero padding option (OPENSSL_ZERO_PADDING) and add the null padding yourself. Note: null padding is not robust because it can not correctly handle all binary data.

Example: openssl_encrypt($data, "aes-256-cbc", $encryption_key, OPENSSL_ZERO_PADDING, $iv);


mcrypt_encrypt():

Base64: Od2i8FHmWvMeXt+HwCy7k93koPVClK1erHsZwoB6sUE=
Hex: 39DDA2F051E65AF31E5EDF87C02CBB93 DDE4A0F54294AD5EAC7B19C2807AB141

openssl_encrypt:

Base64: Od2i8FHmWvMeXt+HwCy7kyCt0nvHTaO4IdjdiF15LAc=
Hex: 39DDA2F051E65AF31E5EDF87C02CBB93 20ADD27BC74DA3B821D8DD885D792C07



Related Topics



Leave a reply



Submit