PHP AES-128 CBC with HMAC File Encryption
Make sure mcrypt is installed and enabled.
#ubuntu: sudo apt-get install php5-mcrypt; sudo php5enmod mcrypt; sudo service apache2 restart;#apache php -r "print_r(stream_get_filters());"; #cli php -r "print_r(mcrypt_list_algorithms());"; #cli
This class will use the mcrypt php library to encrypt a string (or file) using AES-128 CBC 128 with an HMAC. For AES-128 use a 16 byte key, AES-192 use a 24 byte key, and AES-256 use an 32 byte key.
<?php
$key16 = openssl_random_pseudo_bytes($length=16);
$encrypted = AES::encrypt128CBC($input, $key16);
$output = AES::decrypt128CBC($encrypted, $key16);
echo $encrypted."\n";
echo $output."\n";
$input = 'filecontents';
file_put_contents('f.txt', $input);
AES::encryptFile128CBC($finput='f.txt', $foutput='f.aes', $key16);
AES::decryptFile128CBC($finput='f.aes', $foutput='f.out', $key16);
echo file_get_contents($foutput)."\n";
?><?php
//http://php.net/manual/en/book.mcrypt.php
//http://php.net/manual/en/filters.encryption.php
//http://php.net/manual/en/function.stream-get-filters.php
class AES
{
public static function encrypt128CBC($data, $key){ return self::encryptCBC($data, $key, 16); }
public static function encrypt192CBC($data, $key){ return self::encryptCBC($data, $key, 24); }
public static function encrypt256CBC($data, $key){ return self::encryptCBC($data, $key, 32); }
public static function decrypt128CBC($data, $key){ return self::decryptCBC($data, $key, 16); }
public static function decrypt192CBC($data, $key){ return self::decryptCBC($data, $key, 24); }
public static function decrypt256CBC($data, $key){ return self::decryptCBC($data, $key, 32); }
public static function encryptFile128CBC($ifname, $ofname, $key){ return self::encryptFileCBC($ifname, $ofname, $key, 16); }
public static function encryptFile192CBC($ifname, $ofname, $key){ return self::encryptFileCBC($ifname, $ofname, $key, 24); }
public static function encryptFile256CBC($ifname, $ofname, $key){ return self::encryptFileCBC($ifname, $ofname, $key, 32); }
public static function decryptFile128CBC($ifname, $ofname, $key){ return self::decryptFileCBC($ifname, $ofname, $key, 16); }
public static function decryptFile192CBC($ifname, $ofname, $key){ return self::decryptFileCBC($ifname, $ofname, $key, 24); }
public static function decryptFile256CBC($ifname, $ofname, $key){ return self::decryptFileCBC($ifname, $ofname, $key, 32); }
private static function encryptCBC($data, $key, $key_size)
{
$cipher = MCRYPT_RIJNDAEL_128;
$mode = MCRYPT_MODE_CBC;
$iv_size = mcrypt_get_iv_size($cipher, $mode);
$iv = mcrypt_create_iv($iv_size, MCRYPT_DEV_URANDOM);
$block_size = mcrypt_get_block_size($cipher, $mode);
$padding = $block_size - (strlen($data) % $block_size);
$data .= str_repeat(chr($padding), $padding);//PKCS7 Padding
$encrypted = mcrypt_encrypt($cipher, $key, $data, $mode, $iv);
$hmac = hash_hmac('sha256', $encrypted, $key, $raw=true);
$encoded = strtr(base64_encode($hmac.$iv.$encrypted), '+/=', '._-');
return strlen($key)==$key_size ? $encoded : '';
}
private static function decryptCBC($data, $key, $key_size)
{
$hash_size = 32;
$cipher = MCRYPT_RIJNDAEL_128;
$mode = MCRYPT_MODE_CBC;
$iv_size = mcrypt_get_iv_size($cipher, $mode);
$block_size = mcrypt_get_block_size($cipher, $mode);
$decoded = base64_decode( strtr($data, '._-', '+/=') );
$hmac = substr($decoded, 0, $hash_size);
$iv = substr($decoded, $hash_size, $iv_size);
$cmac = hash_hmac('sha256', substr($decoded, $iv_size+$hash_size), $key, $raw=true);
$decrypted = mcrypt_decrypt($cipher, $key, substr($decoded, $iv_size+$hash_size), $mode, $iv);
$padding = ord($decrypted[strlen($decrypted) - 1]);
return $hmac==$cmac ? substr($decrypted, 0, 0-$padding) : '';
}
//see: http://php.net/manual/en/filters.encryption.php
//see: http://php.net/manual/en/function.stream-get-filters.php
private static function encryptFileCBC($input_stream, $aes_filename, $key, $key_size)
{
$hash_size = 32;
$cipher = MCRYPT_RIJNDAEL_128;
$mode = MCRYPT_MODE_CBC;
$block_size = mcrypt_get_block_size($cipher, $mode);
$iv_size = mcrypt_get_iv_size($cipher, $mode);
$iv = mcrypt_create_iv($iv_size, MCRYPT_DEV_URANDOM);
$opts= array('mode'=>$mode, 'iv'=>$iv, 'key'=>$key);
$infilesize = 0;
$fin = fopen($input_stream, "rb");
$fcrypt = fopen($aes_filename, "wb+");
if (!empty($fin) && !empty($fcrypt) && strlen($key)==$key_size)
{
fwrite($fcrypt, str_repeat("_", $hash_size) );//placeholder, HMAC will go here later
fwrite($fcrypt, $iv);
stream_filter_append($fcrypt, 'mcrypt.'.$cipher, STREAM_FILTER_WRITE, $opts);
while (!feof($fin))
{
$block = fread($fin, 8192);
$infilesize+=strlen($block);
fwrite($fcrypt, $block);
}
$padding = $block_size - ($infilesize % $block_size);//$padding is a number from 0-15
if (feof($fin) && $padding>0)
{
fwrite($fcrypt, str_repeat(chr($padding), $padding) );//perform PKCS7 padding
}
fclose($fin);
fclose($fcrypt);
$stream = 'php://filter/read=user-filter.ignorefirst32bytes/resource=' . $aes_filename;
$hmac_raw = hash_hmac_file('sha256', $stream, $key, $raw=true);
$fcrypt = fopen($aes_filename, "rb+");
fwrite($fcrypt, $hmac_raw);
fclose($fcrypt);
return 1;
}
return 0;
}
private static function decryptFileCBC($aes_filename, $out_stream, $key, $key_size)
{
$hash_size = 32;
$cipher = MCRYPT_RIJNDAEL_128;
$mode = MCRYPT_MODE_CBC;
$iv_size = mcrypt_get_iv_size($cipher, $mode);
$stream = 'php://filter/read=user-filter.ignorefirst32bytes/resource=' . $aes_filename;
$hmac_calc = hash_hmac_file('sha256', $stream, $key, $raw=true);
$fcrypt = fopen($aes_filename, "rb");
$fout = fopen($out_stream, 'wb');
if (!empty($fout) && !empty($fcrypt) && strlen($key)==$key_size)
{
$hmac_raw = fread($fcrypt, $hash_size);
$iv = fread($fcrypt, $iv_size);
$opts = $hmac_calc==$hmac_raw ? array('mode'=>$mode, 'iv'=>$iv, 'key'=>$key) : array();
stream_filter_append($fcrypt, 'mdecrypt.'.$cipher, STREAM_FILTER_READ, $opts);
while (!feof($fcrypt))
{
$block = fread($fcrypt, 8192);
if (feof($fcrypt))
{
$padding = ord($block[strlen($block) - 1]);//assume PKCS7 padding
$block = substr($block, 0, 0-$padding);
}
fwrite($fout, $block);
}
fclose($fout);
fclose($fcrypt);
return 1;
}
return 0;
}
}
class AES_HMAC_Skip32Bytes extends PHP_User_Filter
{
private $skipped=0;
function filter($in, $out, &$consumed, $closing)
{
while ($bucket = stream_bucket_make_writeable($in))
{
$outlen = $bucket->datalen;
if ($this->skipped<32)
{
$outlen = min($bucket->datalen,32-$this->skipped);
$bucket->data = substr($bucket->data, $outlen);
$bucket->datalen = $bucket->datalen-$outlen;
$this->skipped+=$outlen;
}
$consumed += $outlen;
stream_bucket_append($out, $bucket);
}
return PSFS_PASS_ON;
}
}
stream_filter_register("user-filter.ignorefirst32bytes", "AES_HMAC_Skip32Bytes");code snippets are licensed under Creative Commons CC-By-SA 3.0 (unless otherwise specified)
|