Archives
Visitors
  • 54849This month:
  • 785Today:
  • 20Currently online:



LeaseWeb CDN

Lessons learned implementing AES in PHP using Mcrypt

The Advanced Encryption Standard (AES) is the successor of triple DES. When you need a standardized, secure, high performance symmetric cipher it seems like a good choice. Wi-Fi network traffic is encrypted with AES for instance. Also when you want to securely store data in a database or on disk you could choose AES. Many SSDs store data internally using AES encryption. PHP supports AES through “mcrypt”. On Debian based systems (like Ubuntu and Mint) you can install it using “sudo apt-get install php5-mcrypt”.

Rijndael-256 is not AES-256

In Mcrypt there is no cipher called “AES”, but Mcrypt supports “Rijndael”. Note that AES is based on the Rijndael cipher. So can we just use Rijndael for AES-128, AES-192 and AES-256? It seems straight forward since Mcrypt supports Rijndael-128, Rijndael-192 and Rijndael-256. But be aware of this pitfall:

AES-256 is different from RIJNDAEL-256. The 256 in AES refers to the key size, where the 256 in RIJNDAEL refers to block size. AES-256 is RIJNDAEL-128 when used with a 256 bit key. – source

So remember that only “Rijndael-128″ in “Cipher-block chaining” (CBC) mode is defined as the Advanced Encryption Standard (AES).

You can see this in effect in the following code:

<?php
echo "RIJNDAEL 128 CBC\n";
$max_key_size = mcrypt_get_key_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC)*8;
echo "Max key size: $max_key_size\n";
$block_size = mcrypt_get_block_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC)*8;
echo "Block size: $block_size\n";

This is the output:

RIJNDAEL 128 CBC
Max key size: 256
Block size: 128

Key padding woes

PHP is a very forgiving language. It allows you to define a variable containing a string and use it as an integer without a single warning. The Mcrypt functions handle different key sizes in the same forgiving way. Only 128, 192, and 256 bit keys are accepted in Rijndael-128, but if you feed Mcrypt a smaller key it will automatically pad it to an acceptable size using “zero” bytes. This is shown in the example below:

<?php
$text = "http://xkcd.com/1323/";
$enc = MCRYPT_RIJNDAEL_128;
$mode = MCRYPT_MODE_CBC;
$iv = mcrypt_create_iv(mcrypt_get_iv_size($enc, $mode), MCRYPT_DEV_URANDOM);
$keys = array();
// smaller than 128 bit key gets padded to 128 bit
$keys[] = pack('H*', "00112233445566778899aabbccddee");
$keys[] = pack('H*', "00112233445566778899aabbccddee00");
// larger than 128 bit key gets padded to 192 bit
$keys[] = pack('H*', "00112233445566778899aabbccddeeff00");
$keys[] = pack('H*', "00112233445566778899aabbccddeeff0000000000000000");
// larger than 192 bit key gets padded to 256 bit
$keys[] = pack('H*', "00112233445566778899aabbccddeeff001122334455667700");
$keys[] = pack('H*', "00112233445566778899aabbccddeeff00112233445566770000000000000000");
// larger than 256 bit key will be truncated (with a warning)
$keys[] = pack('H*', "00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff00");
foreach ($keys as $key)
{ echo bin2hex(mcrypt_encrypt($enc, $key, $text, $mode, $iv))."\n";
}

The output of the above script:

a33d5ae5ca00e4523ea4e14baee2206351ad18b6728c95cafa56761f78d8f743
a33d5ae5ca00e4523ea4e14baee2206351ad18b6728c95cafa56761f78d8f743
6892e09f1ec5003cfdde3b2225fb3a094f0c17b8b12587d25fee1ece1b55146d
6892e09f1ec5003cfdde3b2225fb3a094f0c17b8b12587d25fee1ece1b55146d
794d046201f0dc775be3a6f5173024e07b7304c7e4313ab136333e7fc1df2831
794d046201f0dc775be3a6f5173024e07b7304c7e4313ab136333e7fc1df2831
PHP Warning:  mcrypt_encrypt(): Size of key is too large for this algorithm on line 19
685b12d5967f71ac4e881d228b5bcd7320a4c873f26fe25996229931c4566e92

Null padded strings

Because AES is a block cipher it cannot encrypt and decrypt a three byte plain text. Any plain text should be a multiple of the block size (which is 16). Again Mcrypt accepts any size of plain text and solves this “issue” for you by automatically padding it with zero bytes. Just like it does with the key. When decoding the plain text this may lead to some confusion. You can see this illustrated in the code below:

<?php
$enc = MCRYPT_RIJNDAEL_128;
$key = "secret";
$mode = MCRYPT_MODE_CBC;
$iv = mcrypt_create_iv(mcrypt_get_iv_size($enc, $mode), MCRYPT_DEV_URANDOM);
$text = "123";
$crypt = mcrypt_encrypt($enc, $key, $text, $mode, $iv);
$text = mcrypt_decrypt($enc, $key, $crypt, $mode, $iv);
var_dump($text);
var_dump(bin2hex($text));
var_dump(strtok($text,"\0"));
var_dump(bin2hex(strtok($text,"\0")));

That produces:

string(16) "123"
string(32) "31323300000000000000000000000000"
string(3) "123"
string(6) "313233"

In the above example the decrypted plain text seems to be padded with zero bytes. This is not true. It is actually the plain text, when fed to the cipher, that was padded with zero bytes. This makes the plain text after decrypting equal to the plain text before encrypting.

As you can see above a simple “strtok” call can remove everything from the first zero byte on from the string. NB: You should only do this if you are sure the original plain text does not contain any zero bytes. If it can contain zero bytes then make sure your plain text length is always a multiple of 16 and no padding occurs.

Ignored initialization vector

If the initialization vector does not have the right size (16 bytes), then Mcrypt does not pad it. Instead it outputs a warning and uses an empty initialization vector. This empty initialization vector is a series of 16 zero bytes. This behavior is shown in the code example below:

<?php
$enc = MCRYPT_RIJNDAEL_128;
$key = "secret";
$mode = MCRYPT_MODE_CBC;
$iv = "short_iv";
$text = "123";
$crypt = mcrypt_encrypt($enc, $key, $text, $mode, $iv);
$iv = pack('H*', "00000000000000000000000000000000");
$text = mcrypt_decrypt($enc, $key, $crypt, $mode, $iv);
var_dump($text);

The output of the above code is:

PHP Warning:  mcrypt_encrypt(): The IV parameter must be as long as the blocksize on line 7
string(16) "123"

So make sure you turn on warnings when using Mcrypt, it might hold some very valuable information.

5 Responses to “Lessons learned implementing AES in PHP using Mcrypt”

Leave a Reply