First of all I apologize for the length of this answer.
I just came across this thread and I hope that this class may be of help to anyone reading this thread looking for an answer and source code they can use.
Description:
This class will first take the supplied encryption key and run it through the PBKDF2 implementation using the SHA-512 algorithm at 1000 iterations.
When encrypting data this class will compress the data and compute an md5 digest of the compressed data before encryption. It will also calculate the length of the data after compression. These calculated values are then encrypted with with the compressed data and the IV is prepended to the encrypted output.
A new IV is generated using dev/urandom before each encryption operation. If the script is running on a Windows machine and the PHP version is less than 5.3, the class will use MCRYPT_RAND to generate an IV.
Depending on if parameter $raw_output is true or false, the encryption method will return lowercase hexit by default or raw binary of the encrypted data.
Decryption will reverse the encryption process and check that the computed md5 digest is equal to the stored md5 digest that was encrypted with the data. If the hashes are not the same, the decryption method will return false. It will also use the stored length of the compressed data to ensure all padding is removed before decompression.
This class uses Rijndael 128 in CBC mode.
This class will work cross platform and has been tested on PHP 5.2, 5.3, 5.4, 5.5 and 5.6
File: AesEncryption.php
<?php
/**
* This file contains the class AesEncryption
*
* AesEncryption can safely encrypt and decrypt plain or binary data and
* uses verification to ensure decryption was successful.
*
* PHP version 5
*
* LICENSE: This source file is subject to version 2.0 of the Apache license
* that is available through the world-wide-web at the following URI:
* https://www.apache.org/licenses/LICENSE-2.0.html.
*
* @author Michael Bush <michael(.)bush(@)hotmail(.)co(.)uk>
* @license https://www.apache.org/licenses/LICENSE-2.0.html Apache 2.0
* @copyright 2015 Michael Bush
* @version 1.0.0
*/
/**
* @version 1.0.0
*/
final class AesEncryption
{
/**
* @var string
*/
private $key;
/**
* @var string
*/
private $iv;
/**
* @var resource
*/
private $mcrypt;
/**
* Construct the call optionally providing an encryption key
*
* @param string $key
* @return Encryption
* @throws RuntimeException if the PHP installation is missing critical requirements
*/
public function __construct($key = null) {
if (!extension_loaded ('mcrypt')) {
throw new RuntimeException('MCrypt library is not availble');
}
if (!extension_loaded ('hash')) {
throw new RuntimeException('Hash library is not availble');
}
if (!in_array('rijndael-128', mcrypt_list_algorithms(), true)) {
throw new RuntimeException('MCrypt library does not contain an implementation of rijndael-128');
}
if (!in_array('cbc', mcrypt_list_modes(), true)) {
throw new RuntimeException('MCrypt library does not support CBC encryption mode');
}
$this->mcrypt = mcrypt_module_open('rijndael-128', '', 'cbc', '');
if(isset($key)) {
$this->SetKey($key);
}
}
/**
* @return void
*/
public function __destruct() {
if (extension_loaded ('mcrypt')) {
if (isset($this->mcrypt)) {
mcrypt_module_close($this->mcrypt);
}
}
}
/**
* Set the key to be used for encryption and decryption operations.
*
* @param string $key
* @return void
*/
public function SetKey($key){
$this->key = $this->pbkdf2('sha512', $key, hash('sha512', $key, true), 1000, mcrypt_enc_get_key_size($this->mcrypt), true);
}
/**
* Encrypts data
*
* @param string $data
* @param bool $raw_output if false this method will return lowercase hexit, if true this method will return raw binary
* @return string
*/
public function Encrypt($data, $raw_output = false) {
$data = gzcompress($data, 9);
$hash = md5($data, true);
$datalen = strlen($data);
$datalen = pack('N', $datalen);
$data = $datalen . $hash . $data;
if (version_compare(PHP_VERSION, '5.3.0', '<=')) {
if (strtolower (substr (PHP_OS, 0, 3)) == 'win') {
$this->iv = mcrypt_create_iv(mcrypt_enc_get_iv_size($this->mcrypt), MCRYPT_RAND);
} else {
$this->iv = mcrypt_create_iv(mcrypt_enc_get_iv_size($this->mcrypt), MCRYPT_DEV_URANDOM);
}
} else {
$this->iv = mcrypt_create_iv(mcrypt_enc_get_iv_size($this->mcrypt), MCRYPT_DEV_URANDOM);
}
$this->initialize();
$data = mcrypt_generic($this->mcrypt, $data);
$this->deinitialize();
$data = $this->iv . $data;
$this->iv = null;
if ($raw_output) {
return $data;
}
$data = unpack('H*',$data);
$data = end($data);
return $data;
}
/**
* Decrypts data
*
* @param string $data
* @return string This method will return false if an error occurs
*/
public function Decrypt($data) {
if (ctype_xdigit($data)) {
$data = pack ('H*',$data);
}
$this->iv = substr ($data, 0, mcrypt_enc_get_iv_size($this->mcrypt));
$data = substr ($data, mcrypt_enc_get_iv_size($this->mcrypt));
$this->initialize();
$data = mdecrypt_generic($this->mcrypt, $data);
$this->deinitialize();
$datalen = substr($data, 0, 4);
$len = unpack('N', $datalen);
$len = end($len);
$hash = substr($data, 4, 16);
$data = substr($data, 20, $len);
$datahash = md5($data, true);
if ($this->compare($hash,$datahash)) {
$data = @gzuncompress($data);
return $data;
}
return false;
}
/**
* Initializes the mcrypt module
*
* @return void
*/
private function initialize() {
mcrypt_generic_init($this->mcrypt, $this->key, $this->iv);
}
/**
* Deinitializes the mcrypt module and releases memory.
*
* @return void
*/
private function deinitialize() {
mcrypt_generic_deinit($this->mcrypt);
}
/**
* Implementation of a timing-attack safe string comparison algorithm, it will use hash_equals if it is available
*
* @param string $safe
* @param string $supplied
* @return bool
*/
private function compare($safe, $supplied) {
if (function_exists('hash_equals')) {
return hash_equals($safe, $supplied);
}
$safe .= chr(0x00);
$supplied .= chr(0x00);
$safeLen = strlen($safe);
$suppliedLen = strlen($supplied);
$result = $safeLen - $suppliedLen;
for ($i = 0; $i < $suppliedLen; $i++) {
$result |= (ord($safe[$i % $safeLen]) ^ ord($supplied[$i]));
}
return $result === 0;
}
/**
* Implementation of the keyed-hash message authentication code algorithm, it will use hash_hmac if it is available
*
* @param string $algo
* @param string $data
* @param string $key
* @param bool $raw_output
* @return string
*
* @bug method returning wrong result for joaat algorithm
* @id 101275
* @affects PHP installations without the hash_hmac function but they do have the joaat algorithm
* @action wont fix
*/
private function hmac($algo, $data, $key, $raw_output = false) {
$algo = strtolower ($algo);
if (function_exists('hash_hmac')) {
return hash_hmac($algo, $data, $key, $raw_output);
}
switch ( $algo ) {
case 'joaat':
case 'crc32':
case 'crc32b':
case 'adler32':
case 'fnv132':
case 'fnv164':
case 'fnv1a32':
case 'fnv1a64':
$block_size = 4;
break;
case 'md2':
$block_size = 16;
break;
case 'gost':
case 'gost-crypto':
case 'snefru':
case 'snefru256':
$block_size = 32;
break;
case 'sha384':
case 'sha512':
case 'haval256,5':
case 'haval224,5':
case 'haval192,5':
case 'haval160,5':
case 'haval128,5':
case 'haval256,4':
case 'haval224,4':
case 'haval192,4':
case 'haval160,4':
case 'haval128,4':
case 'haval256,3':
case 'haval224,3':
case 'haval192,3':
case 'haval160,3':
case 'haval128,3':
$block_size = 128;
break;
default:
$block_size = 64;
break;
}
if (strlen($key) > $block_size) {
$key=hash($algo, $key, true);
} else {
$key=str_pad($key, $block_size, chr(0x00));
}
$ipad=str_repeat(chr(0x36), $block_size);
$opad=str_repeat(chr(0x5c), $block_size);
$hmac = hash($algo, ($key^$opad) . hash($algo, ($key^$ipad) . $data, true), $raw_output);
return $hmac;
}
/**
* Implementation of the pbkdf2 algorithm, it will use hash_pbkdf2 if it is available
*
* @param string $algorithm
* @param string $password
* @param string $salt
* @param int $count
* @param int $key_length
* @param bool $raw_output
* @return string
* @throws RuntimeException if the algorithm is not found
*/
private function pbkdf2($algorithm, $password, $salt, $count = 1000, $key_length = 0, $raw_output = false) {
$algorithm = strtolower ($algorithm);
if (!in_array($algorithm, hash_algos(), true)) {
throw new RuntimeException('Hash library does not contain an implementation of ' . $algorithm);
}
if (function_exists('hash_pbkdf2')) {
return hash_pbkdf2($algorithm, $password, $salt, $count, $key_length, $raw_output);
}
$hash_length = strlen(hash($algorithm, '', true));
if ($count <= 0) {
$count = 1000;
}
if($key_length <= 0) {
$key_length = $hash_length * 2;
}
$block_count = ceil($key_length / $hash_length);
$output = '';
for($i = 1; $i <= $block_count; $i++) {
$last = $salt . pack('N', $i);
$last = $xorsum = $this->hmac($algorithm, $last, $password, true);
for ($j = 1; $j < $count; $j++) {
$xorsum ^= ($last = $this->hmac($algorithm, $last, $password, true));
}
$output .= $xorsum;
}
if ($raw_output) {
return substr($output, 0, $key_length);
}
$output = unpack('H*',$output);
$output = end ($output);
return substr($output, 0, $key_length);
}
}
Example usage:
<?php
include 'AesEncryption.php';
$key = 'my secret key';
$string = 'hello world';
try
{
$aes = new AesEncryption($key); // exception can be thrown here if the class is not supported
$data = $aes->Encrypt($string, true); // expecting return of a raw byte string
$decr = $aes->Decrypt($data); // expecting the return of "hello world"
var_dump ($decr);
// encrypt something else with a different key
$aes->SetKey('my other secret key'); // exception can be thrown here if the class is not supported
$data2 = $aes->Encrypt($string); // return the return of a lowercase hexit string
$decr = $aes->Decrypt($data2); // expecting the return of "hello world"
var_dump ($decr);
// proof that the key was changed
$decr = $aes->Decrypt($data); // expecting return of Boolean False
var_dump ($decr);
// reset the key back
$aes->SetKey($key); // exception can be thrown here if the class is not supported
$decr = $aes->Decrypt($data); // expecting hello world
var_dump ($decr);
}
catch (Exception $e)
{
print 'Error running AesEncryption class; reason: ' . $e->getMessage ();
}