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)