From 215d43eb066369681b0a7637392a287bd36abf5d Mon Sep 17 00:00:00 2001 From: mark_story Date: Tue, 27 Aug 2013 21:20:22 -0400 Subject: [PATCH] Add encrypt() and decrypt() methods. These methods use AES-256 and provide a simple to use API with easy to remember names. --- lib/Cake/Test/Case/Utility/SecurityTest.php | 67 +++++++++++++++++++++ lib/Cake/Utility/Security.php | 65 ++++++++++++++++++++ 2 files changed, 132 insertions(+) diff --git a/lib/Cake/Test/Case/Utility/SecurityTest.php b/lib/Cake/Test/Case/Utility/SecurityTest.php index f1a0e33f8..3f31219bd 100644 --- a/lib/Cake/Test/Case/Utility/SecurityTest.php +++ b/lib/Cake/Test/Case/Utility/SecurityTest.php @@ -302,4 +302,71 @@ class SecurityTest extends CakeTestCase { Security::rijndael($txt, $key, 'encrypt'); } +/** + * Test encrypt/decrypt. + * + * @return void + */ + public function testEncryptDecrypt() { + $txt = 'The quick brown fox'; + $key = 'This key is longer than 32 bytes long.'; + $result = Security::encrypt($txt, $key); + $this->assertNotEquals($txt, $result, 'Should be encrypted.'); + $this->assertNotEquals($result, Security::encrypt($txt, $key), 'Each result is unique.'); + $this->assertEquals($txt, Security::decrypt($result, $key)); + } + +/** + * Test that short keys cause errors + * + * @expectedException CakeException + * @expectedExceptionMessage Invalid key for encrypt(), key must be at least 256 bits (32 bytes) long. + * @return void + */ + public function testEncryptInvalidKey() { + $txt = 'The quick brown fox jumped over the lazy dog.'; + $key = 'this is too short'; + Security::encrypt($txt, $key); + } + +/** + * Test that empty data cause errors + * + * @expectedException CakeException + * @expectedExceptionMessage The data to encrypt cannot be empty. + * @return void + */ + public function testEncryptInvalidData() { + $txt = ''; + $key = 'This is a key that is long enough to be ok.'; + Security::encrypt($txt, $key); + } + + +/** + * Test that short keys cause errors + * + * @expectedException CakeException + * @expectedExceptionMessage Invalid key for decrypt(), key must be at least 256 bits (32 bytes) long. + * @return void + */ + public function testDecryptInvalidKey() { + $txt = 'The quick brown fox jumped over the lazy dog.'; + $key = 'this is too short'; + Security::decrypt($txt, $key); + } + +/** + * Test that empty data cause errors + * + * @expectedException CakeException + * @expectedExceptionMessage The data to decrypt cannot be empty. + * @return void + */ + public function testDecryptInvalidData() { + $txt = ''; + $key = 'This is a key that is long enough to be ok.'; + Security::decrypt($txt, $key); + } + } diff --git a/lib/Cake/Utility/Security.php b/lib/Cake/Utility/Security.php index 5df50f923..cc47db7c1 100644 --- a/lib/Cake/Utility/Security.php +++ b/lib/Cake/Utility/Security.php @@ -289,4 +289,69 @@ class Security { return crypt($password, $salt); } +/** + * Encrypt a value using AES-256. + * + * *Caveat* You cannot properly encrypt/decrypt data with trailing null bytes. + * Any trailing null bytes will be removed on decryption due to how PHP pads messages + * with nulls prior to encryption. + * + * @param string $plain The value to encrypt. + * @param string $key The 256 bit/32 byte key to use as a cipher key. + * @return string Encrypted data. + * @throws CakeException On invalid data or key. + */ + public static function encrypt($plain, $key) { + static::_checkKey($key, 'encrypt()'); + if (empty($plain)) { + throw new CakeException(__d('cake_dev', 'The data to encrypt cannot be empty.')); + } + $key = substr($key, 0, 32); + $algorithm = MCRYPT_RIJNDAEL_128; + $mode = MCRYPT_MODE_CBC; + + $ivSize = mcrypt_get_iv_size($algorithm, $mode); + $iv = mcrypt_create_iv($ivSize, MCRYPT_RAND); + return $iv . mcrypt_encrypt($algorithm, $key, $plain, $mode, $iv); + } + +/** + * Check the encryption key for proper length. + * + * @param string $key + * @param string $method The method the key is being checked for. + * @return void + * @throws CakeException When key length is not 256 bit/32 bytes + */ + protected static function _checkKey($key, $method) { + if (strlen($key) < 32) { + throw new CakeException(__d('cake_dev', 'Invalid key for %s, key must be at least 256 bits (32 bytes) long.', $method)); + } + } + +/** + * Decrypt a value using AES-256. + * + * @param string $cipher The ciphertext to decrypt. + * @param string $key The 256 bit/32 byte key to use as a cipher key. + * @return string Decrypted data. Any trailing null bytes will be removed. + * @throws CakeException On invalid data or key. + */ + public static function decrypt($cipher, $key) { + static::_checkKey($key, 'decrypt()'); + if (empty($cipher)) { + throw new CakeException(__d('cake_dev', 'The data to decrypt cannot be empty.')); + } + $key = substr($key, 0, 32); + + $algorithm = MCRYPT_RIJNDAEL_128; + $mode = MCRYPT_MODE_CBC; + $ivSize = mcrypt_get_iv_size($algorithm, $mode); + + $iv = substr($cipher, 0, $ivSize); + $cipher = substr($cipher, $ivSize); + $plain = mcrypt_decrypt($algorithm, $key, $cipher, $mode, $iv); + return rtrim($plain, "\0"); + } + }