Created
May 25, 2013 07:08
-
-
Save djekl/5648226 to your computer and use it in GitHub Desktop.
Data encryption in PHP - https://defuse.ca/secure-php-encryption.htm
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
/* | |
* Data encryption in PHP. | |
* | |
* This script is released into the public domain by Defuse Security. | |
* You may use it for any purpose whatsoever and redistribute it with or without | |
* modification. | |
* | |
* https://defuse.ca/secure-php-encryption.htm | |
*/ | |
// Configuration constants. Don't change unless you know what you're doing. | |
// The block cipher to use for encryption. | |
define('CRYPTO_CIPHER_ALG', MCRYPT_RIJNDAEL_128); | |
// The hash function to use for HMAC. | |
define('CRYPTO_HMAC_ALG', 'sha256'); | |
// The byte length of the encryption and HMAC keys. | |
define('CRYPTO_KEY_BYTE_SIZE', 16); | |
// The block cipher mode of operation to use. | |
define('CRYPTO_CIPHER_MODE', 'cbc'); | |
// The length of an HMAC, so it can be extracted from the ciphertext. | |
define('CRYPTO_HMAC_BYTES', strlen(hash_hmac(CRYPTO_HMAC_ALG, '', '', TRUE))); | |
class Crypto | |
{ | |
// Ciphertext format: [____HMAC____][____IV____][____CIPHERTEXT____]. | |
public static function Encrypt($plaintext, $key) | |
{ | |
$key = self::AdjustKey($key, 16); | |
if(strlen($key) < 16) | |
{ | |
trigger_error("Key too small.", E_USER_ERROR); | |
return FALSE; | |
} | |
// Open the encryption module and get some parameters. | |
$crypt = mcrypt_module_open(CRYPTO_CIPHER_ALG, "", CRYPTO_CIPHER_MODE, ""); | |
$keysize = mcrypt_enc_get_key_size($crypt); | |
$ivsize = mcrypt_enc_get_iv_size($crypt); | |
// Generate a sub-key for encryption. | |
$ekey = self::CreateSubkey($key, "encryption", $keysize); | |
// Generate a random initialization vector. | |
$iv = self::SecureRandom($ivsize); | |
// Pad the plaintext to a multiple of the block size (PKCS #7) | |
$block = mcrypt_enc_get_block_size($crypt); | |
$pad = $block - (strlen($plaintext) % $block); | |
$plaintext .= str_repeat(chr($pad), $pad); | |
// Do the encryption. | |
mcrypt_generic_init($crypt, $ekey, $iv); | |
$ciphertext = $iv . mcrypt_generic($crypt, $plaintext); | |
mcrypt_generic_deinit($crypt); | |
mcrypt_module_close($crypt); | |
// Generate a sub-key for authentication. | |
$akey = self::CreateSubkey($key, "authentication", CRYPTO_KEY_BYTE_SIZE); | |
// Apply the HMAC. | |
$auth = hash_hmac(CRYPTO_HMAC_ALG, $ciphertext, $akey, TRUE); | |
$ciphertext = $auth . $ciphertext; | |
return $ciphertext; | |
} | |
public static function Decrypt($ciphertext, $key) | |
{ | |
// Extract the HMAC from the front of the ciphertext. | |
if(strlen($ciphertext) <= CRYPTO_HMAC_BYTES) | |
return FALSE; | |
$key = self::AdjustKey($key, 16); | |
$hmac = substr($ciphertext, 0, CRYPTO_HMAC_BYTES); | |
$ciphertext = substr($ciphertext, CRYPTO_HMAC_BYTES); | |
// Re-generate the same authentication sub-key. | |
$akey = self::CreateSubkey($key, "authentication", CRYPTO_KEY_BYTE_SIZE); | |
// Make sure the HMAC is correct. If not, the ciphertext has been changed. | |
if($hmac === hash_hmac(CRYPTO_HMAC_ALG, $ciphertext, $akey, TRUE)) | |
{ | |
// Open the encryption module and get some parameters. | |
$crypt = mcrypt_module_open(CRYPTO_CIPHER_ALG, "", CRYPTO_CIPHER_MODE, ""); | |
$keysize = mcrypt_enc_get_key_size($crypt); | |
$ivsize = mcrypt_enc_get_iv_size($crypt); | |
// Re-generate the same encryption sub-key. | |
$ekey = self::CreateSubkey($key, "encryption", $keysize); | |
// Extract the initialization vector from the ciphertext. | |
if(strlen($ciphertext) <= $ivsize) | |
return FALSE; | |
$iv = substr($ciphertext, 0, $ivsize); | |
$ciphertext = substr($ciphertext, $ivsize); | |
// Do the decryption. | |
mcrypt_generic_init($crypt, $ekey, $iv); | |
$plaintext = mdecrypt_generic($crypt, $ciphertext); | |
mcrypt_generic_deinit($crypt); | |
mcrypt_module_close($crypt); | |
// Remove the padding. | |
$pad = ord($plaintext[strlen($plaintext) - 1]); | |
$plaintext = substr($plaintext, 0, strlen($plaintext) - $pad); | |
return $plaintext; | |
} | |
else | |
{ | |
// If the ciphertext has been modified, refuse to decrypt it. | |
return FALSE; | |
} | |
} | |
/* | |
* Creates a sub-key from a master key for a specific purpose. | |
*/ | |
public static function CreateSubkey($master, $purpose, $bytes) | |
{ | |
$source = self::AdjustKey(hash_hmac("sha512", $purpose, $master, TRUE), $bytes); | |
if(strlen($source) < $bytes) { | |
trigger_error("Subkey too big.", E_USER_ERROR); | |
return $source; // fail safe | |
} | |
return substr($source, 0, $bytes); | |
} | |
/* | |
* Returns a random binary string of length $octets bytes. | |
*/ | |
public static function SecureRandom($octets) | |
{ | |
return mcrypt_create_iv($octets, MCRYPT_DEV_URANDOM); | |
} | |
/* | |
* A simple test and demonstration of how to use this class. | |
*/ | |
public static function Test() | |
{ | |
echo "Running crypto test...\n"; | |
$key = mcrypt_create_iv(16, MCRYPT_DEV_URANDOM); | |
$data = "EnCrYpT EvErYThInG\x00\x00"; | |
$ciphertext = Crypto::Encrypt($data, $key); | |
echo "Ciphertext: " . bin2hex($ciphertext) . "\n"; | |
$decrypted = Crypto::Decrypt($ciphertext, $key); | |
echo "Decrypted: " . $decrypted . "\n"; | |
if($decrypted != $data) | |
{ | |
echo "FAIL: Decrypted data is not the same as the original.\n"; | |
return FALSE; | |
} | |
if(Crypto::Decrypt($ciphertext . "a", $key) !== FALSE) | |
{ | |
echo "FAIL: Ciphertext tampering not detected.\n"; | |
return FALSE; | |
} | |
$ciphertext[0] = chr((ord($ciphertext[0]) + 1) % 256); | |
if(Crypto::Decrypt($ciphertext, $key) !== FALSE) | |
{ | |
echo "FAIL: Ciphertext tampering not detected.\n"; | |
return FALSE; | |
} | |
$key = mcrypt_create_iv(16, MCRYPT_DEV_URANDOM); | |
$data = "abcdef"; | |
$ciphertext = Crypto::Encrypt($data, $key); | |
$wrong_key = mcrypt_create_iv(16, MCRYPT_DEV_URANDOM); | |
if (Crypto::Decrypt($ciphertext, $wrong_key)) | |
{ | |
echo "FAIL: Ciphertext decrypts with an incorrect key.\n"; | |
return FALSE; | |
} | |
echo "PASS\n"; | |
return TRUE; | |
} | |
private static function AdjustKey($key, $length = 16) | |
{ | |
if(strlen($key) > $length) | |
$key = str_pad(sha1($key, TRUE), $length, chr(0)); | |
if(strlen($key) < $length) | |
$key = str_pad($key, $length, chr(0)); | |
return $key; | |
} | |
} | |
// Run the test when and only when this script is executed on the command line. | |
if(isset($argv) && realpath($argv[0]) == __FILE__) | |
{ | |
Crypto::Test(); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment