Matching encryption and decryption across PHP, Java and .Net
I have already posted some articles about standardising encryption and decryption across various platforms but came across another gotcha. The reality is that there are standards but the default setup and the names for these standards (unsurprisingly) differ and that's what makes it difficult.
I already use the .Net port of the OWASP ESAPI and that was useful because it gave me a known good implementation to work from. Well, I say OWASP but I actually changed it in several ways, firstly to add signed encryption and also to hard-code AES256 rather than having lots of configuration that i didn't actually need.
I managed to write a PHP decryption function which worked with data that was encrypted using .Net but I had problems when I then tried to write an encrypt function in PHP which would produce data that could be decrypted, also in PHP, using the previously written function.
Because I knew that the other functions worked correctly, this helped. Also, the PHP code required from the examples on php.net were brief enough that there weren't too many variables but I still resorted to echoing out the data at each stage to see where something didn't look write. I ended up getting stung with the string padding!
What mcrypt_encrypt does is pad the input data up to the block size of the cipher, in my case MCRYPT_RIJNDAEL_128 but using the null terminator (\0). This is confusing because when it is decrypted, the decrypted string uses the \0 to terminate the string whereas my decryption function was expecting to remove PKCS #7 padding which is added in the .Net implementation from OWASP. This adds a character whose value is equal to the number of padding characters (making it easy to work out how many there are).
My encrypt function didn't have this and fortunately, there was an example on php.net under the comments for mcrypt_encrypt() (thanks Antonio). I ended up with the following PHP:
protected function encrypt($plaintext)
{
try
{
// PKCS #7 padding for the string so that decryption works!
$block = mcrypt_get_block_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC);
$pad = $block - (strlen($plaintext) % $block);
$plaintext .= str_repeat(chr($pad), $pad);
// Create key and IV
$key = $this->pbkdf2(Config('MasterPassword'), Config('MasterSalt'),1000,32);
$size = mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC);
$ivBytes = mcrypt_create_iv($size, MCRYPT_RAND);
$encrypted = mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $key, $plaintext, MCRYPT_MODE_CBC, $ivBytes);
$encrypted_combined = $ivBytes . $encrypted;
return base64_encode($encrypted_combined);
}
catch (Exception $e)
{
return null;
}
}
Note that the pbkdf2 is the password based key derivation function from here: http://en.wikipedia.org/wiki/PBKDF2 There are various implementations available. I'm not sure whether the PHP one works as expected since I ended up using one from someone else's project.
I already use the .Net port of the OWASP ESAPI and that was useful because it gave me a known good implementation to work from. Well, I say OWASP but I actually changed it in several ways, firstly to add signed encryption and also to hard-code AES256 rather than having lots of configuration that i didn't actually need.
I managed to write a PHP decryption function which worked with data that was encrypted using .Net but I had problems when I then tried to write an encrypt function in PHP which would produce data that could be decrypted, also in PHP, using the previously written function.
Because I knew that the other functions worked correctly, this helped. Also, the PHP code required from the examples on php.net were brief enough that there weren't too many variables but I still resorted to echoing out the data at each stage to see where something didn't look write. I ended up getting stung with the string padding!
What mcrypt_encrypt does is pad the input data up to the block size of the cipher, in my case MCRYPT_RIJNDAEL_128 but using the null terminator (\0). This is confusing because when it is decrypted, the decrypted string uses the \0 to terminate the string whereas my decryption function was expecting to remove PKCS #7 padding which is added in the .Net implementation from OWASP. This adds a character whose value is equal to the number of padding characters (making it easy to work out how many there are).
My encrypt function didn't have this and fortunately, there was an example on php.net under the comments for mcrypt_encrypt() (thanks Antonio). I ended up with the following PHP:
protected function encrypt($plaintext)
{
try
{
// PKCS #7 padding for the string so that decryption works!
$block = mcrypt_get_block_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC);
$pad = $block - (strlen($plaintext) % $block);
$plaintext .= str_repeat(chr($pad), $pad);
// Create key and IV
$key = $this->pbkdf2(Config('MasterPassword'), Config('MasterSalt'),1000,32);
$size = mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC);
$ivBytes = mcrypt_create_iv($size, MCRYPT_RAND);
$encrypted = mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $key, $plaintext, MCRYPT_MODE_CBC, $ivBytes);
$encrypted_combined = $ivBytes . $encrypted;
return base64_encode($encrypted_combined);
}
catch (Exception $e)
{
return null;
}
}
Note that the pbkdf2 is the password based key derivation function from here: http://en.wikipedia.org/wiki/PBKDF2 There are various implementations available. I'm not sure whether the PHP one works as expected since I ended up using one from someone else's project.