diff options
-rw-r--r-- | system/libraries/Encryption.php | 816 | ||||
-rw-r--r-- | system/libraries/Session/drivers/Session_cookie.php | 55 | ||||
-rw-r--r-- | tests/codeigniter/libraries/Encryption_test.php | 130 | ||||
-rw-r--r-- | tests/mocks/autoloader.php | 53 | ||||
-rw-r--r-- | user_guide_src/source/changelog.rst | 17 | ||||
-rw-r--r-- | user_guide_src/source/installation/upgrade_200.rst | 6 | ||||
-rw-r--r-- | user_guide_src/source/installation/upgrade_300.rst | 19 | ||||
-rw-r--r-- | user_guide_src/source/libraries/encrypt.rst (renamed from user_guide_src/source/libraries/encryption.rst) | 0 |
8 files changed, 1045 insertions, 51 deletions
diff --git a/system/libraries/Encryption.php b/system/libraries/Encryption.php new file mode 100644 index 000000000..503fb64e3 --- /dev/null +++ b/system/libraries/Encryption.php @@ -0,0 +1,816 @@ +<?php +/** + * CodeIgniter + * + * An open source application development framework for PHP 5.2.4 or newer + * + * NOTICE OF LICENSE + * + * Licensed under the Open Software License version 3.0 + * + * This source file is subject to the Open Software License (OSL 3.0) that is + * bundled with this package in the files license.txt / license.rst. It is + * also available through the world wide web at this URL: + * http://opensource.org/licenses/OSL-3.0 + * If you did not receive a copy of the license and are unable to obtain it + * through the world wide web, please send an email to + * licensing@ellislab.com so we can send you a copy immediately. + * + * @package CodeIgniter + * @author EllisLab Dev Team + * @copyright Copyright (c) 2008 - 2013, EllisLab, Inc. (http://ellislab.com/) + * @license http://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0) + * @link http://codeigniter.com + * @since Version 3.0 + * @filesource + */ +defined('BASEPATH') OR exit('No direct script access allowed'); + +/** + * CodeIgniter Encryption Class + * + * Provides two-way keyed encryption via PHP's MCrypt and/or OpenSSL extensions. + * + * @package CodeIgniter + * @subpackage Libraries + * @category Libraries + * @author Andrey Andreev + * @link http://codeigniter.com/user_guide/libraries/encryption.html + */ +class CI_Encryption { + + /** + * Encryption cipher + * + * @var string + */ + protected $_cipher = 'rijndael-128'; + + /** + * Cipher mode + * + * @var string + */ + protected $_mode = 'cbc'; + + /** + * Cipher handle + * + * @var mixed + */ + protected $_handle; + + /** + * Encryption key + * + * @var string + */ + protected $_key; + + /** + * PHP extension to be used + * + * @var string + */ + protected $_driver; + + /** + * List of usable drivers (PHP extensions) + * + * @var array + */ + protected $_drivers = array(); + + /** + * List of supported HMAC algorightms + * + * name => digest size pairs + * + * @var array + */ + protected $_digests = array( + 'sha224' => 28, + 'sha256' => 32, + 'sha384' => 48, + 'sha512' => 64 + ); + + // -------------------------------------------------------------------- + + /** + * Class constructor + * + * @param array $params Configuration parameters + * @return void + */ + public function __construct(array $params = array()) + { + $this->_drivers = array( + 'mcrypt' => extension_loaded('mcrypt'), + // While OpenSSL is available for PHP 5.3.0, an IV parameter + // for the encrypt/decrypt functions is only available since 5.3.3 + 'openssl' => (is_php('5.3.3') && extension_loaded('openssl')) + ); + + if ( ! $this->_drivers['mcrypt'] && ! $this->_drivers['openssl']) + { + return show_error('Encryption: Unable to find an available encryption driver.'); + } + // Our configuration validates against the existence of MCRYPT_MODE_* constants, + // but MCrypt supports CTR mode without actually having a constant for it, so ... + elseif ($this->_drivers['mcrypt'] && ! defined('MCRYPT_MODE_CTR')) + { + define('MCRYPT_MODE_CTR', 'ctr'); + } + + $this->initialize($params); + + isset($this->_key) OR $this->_key = config_item('encryption_key'); + if (empty($this->_key)) + { + return show_error('Encryption: You are required to set an encryption key in your configuration.'); + } + + log_message('debug', 'Encryption Class Initialized'); + } + + // -------------------------------------------------------------------- + + /** + * Initialize + * + * @param array $params Configuration parameters + * @return CI_Encryption + */ + public function initialize(array $params) + { + if ( ! empty($params['driver'])) + { + if (isset($this->_drivers[$params['driver']])) + { + if ($this->_drivers[$params['driver']]) + { + $this->_driver = $params['driver']; + } + else + { + log_message('error', "Encryption: Driver '".$params['driver']."' is not available."); + } + } + else + { + log_message('error', "Encryption: Unknown driver '".$params['driver']."' cannot be configured."); + } + } + + if (empty($this->_driver)) + { + $this->_driver = ($this->_drivers['mcrypt'] === TRUE) + ? 'mcrypt' + : 'openssl'; + + log_message('debug', "Encryption: Auto-configured driver '".$this->_driver."'."); + } + + empty($params['key']) OR $this->_key = $params['key']; + $this->{'_'.$this->_driver.'_initialize'}($params); + return $this; + } + + // -------------------------------------------------------------------- + + /** + * Initialize MCrypt + * + * @param array $params Configuration parameters + * @return void + */ + protected function _mcrypt_initialize($params) + { + if ( ! empty($params['cipher'])) + { + $params['cipher'] = strtolower($params['cipher']); + $this->_cipher_alias($params['cipher']); + + if ( ! in_array($params['cipher'], mcrypt_list_algorithms(), TRUE)) + { + log_message('error', 'Encryption: MCrypt cipher '.strtoupper($params['cipher']).' is not available.'); + } + else + { + $this->_cipher = $params['cipher']; + } + } + + if ( ! empty($params['mode'])) + { + if ( ! defined('MCRYPT_MODE_'.$params['mode'])) + { + log_message('error', 'Encryption: MCrypt mode '.strtotupper($params['mode']).' is not available.'); + } + else + { + $this->_mode = constant('MCRYPT_MODE_'.$params['mode']); + } + } + + if (isset($this->_cipher, $this->_mode)) + { + if (is_resource($this->_handle) + && (strtolower(mcrypt_enc_get_algorithms_name($this->_handle)) !== $this->_cipher + OR strtolower(mcrypt_enc_get_modes_name($this->_handle)) !== $this->_mode) + ) + { + mcrypt_module_close($this->_handle); + } + + if ($this->_handle = mcrypt_module_open($this->_cipher, '', $this->_mode, '')) + { + log_message('debug', 'Encryption: MCrypt cipher '.strtoupper($this->_cipher).' initialized in '.strtoupper($this->_mode).' mode.'); + } + else + { + log_message('error', 'Encryption: Unable to initialize MCrypt with cipher '.strtoupper($this->_cipher).' in '.strtoupper($this->_mode).' mode.'); + } + } + } + + // -------------------------------------------------------------------- + + /** + * Initialize OpenSSL + * + * @param array $params Configuration parameters + * @return void + */ + protected function _openssl_initialize($params) + { + if ( ! empty($params['cipher'])) + { + $params['cipher'] = strtolower($params['cipher']); + $this->_cipher_alias($params['cipher']); + $this->_cipher = $params['cipher']; + } + + if ( ! empty($params['mode'])) + { + $this->_mode = strtolower($params['mode']); + } + + if (isset($this->_cipher, $this->_mode)) + { + // OpenSSL methods aren't suffixed with '-stream' for this mode + $handle = ($this->_mode === 'stream') + ? $this->_cipher + : $this->_cipher.'-'.$this->_mode; + + if ( ! in_array($handle, openssl_get_cipher_methods(), TRUE)) + { + $this->_handle = NULL; + log_message('error', 'Encryption: Unable to initialize OpenSSL with method '.strtoupper($handle).'.'); + } + else + { + $this->_handle = $handle; + log_message('debug', 'Encryption: OpenSSL initialized with method '.strtoupper($handle).'.'); + } + } + } + + // -------------------------------------------------------------------- + + /** + * Encrypt + * + * @param string $data Input data + * @param array $params Input parameters + * @return string + */ + public function encrypt($data, array $params = NULL) + { + if (($params = $this->_get_params($params)) === FALSE) + { + return FALSE; + } + elseif ( ! isset($params['iv'])) + { + $params['iv'] = ($iv_size = $this->{'_'.$this->_driver.'_get_iv_size'}($params['handle'])) + ? $this->{'_'.$this->_driver.'_get_iv'}($iv_size) + : NULL; + } + + if (($data = $this->{'_'.$this->_driver.'_encrypt'}($data, $params)) === FALSE) + { + return FALSE; + } + + if ($params['base64']) + { + $data = base64_encode($data); + } + + if ($params['hmac'] !== FALSE) + { + if ( ! isset($params['hmac']['key'])) + { + $params['hmac']['key'] = $this->hkdf( + $params['key'], + $params['hmac']['digest'], + NULL, + NULL, + 'authentication' + ); + } + + return hash_hmac($params['hmac']['digest'], $data, $params['hmac']['key'], ! $params['base64']).$data; + } + + return $data; + } + + // -------------------------------------------------------------------- + + /** + * Encrypt via MCrypt + * + * @param string $data Input data + * @param array $params Input parameters + * @return string + */ + protected function _mcrypt_encrypt($data, $params) + { + if ( ! is_resource($params['handle'])) + { + return FALSE; + } + elseif (mcrypt_generic_init($params['handle'], $params['key'], $params['iv']) < 0) + { + if ($params['handle'] !== $this->_handle) + { + mcrypt_module_close($params['handle']); + } + + return FALSE; + } + + // Use PKCS#7 padding in order to ensure compatibility with OpenSSL + // and other implementations outside of PHP + $block_size = mcrypt_enc_get_block_size($params['handle']); + $pad = $block_size - (strlen($data) % $block_size); + $data .= str_repeat(chr($pad), $pad); + + $data = $params['iv'].mcrypt_generic($params['handle'], $data); + + mcrypt_generic_deinit($params['handle']); + if ($params['handle'] !== $this->_handle) + { + mcrypt_module_close($params['handle']); + } + + return $data; + } + + // -------------------------------------------------------------------- + + /** + * Encrypt via OpenSSL + * + * @param string $data Input data + * @param array $params Input parameters + * @return string + */ + protected function _openssl_encrypt($data, $params) + { + if (empty($params['handle'])) + { + return FALSE; + } + + $data = openssl_encrypt( + $data, + $params['handle'], + $params['key'], + 1, // DO NOT TOUCH! + $params['iv'] + ); + + if ($data === FALSE) + { + return FALSE; + } + + return $params['iv'].$data; + } + + // -------------------------------------------------------------------- + + /** + * Decrypt + * + * @param string $data Encrypted data + * @param array $params Input parameters + * @return string + */ + public function decrypt($data, array $params = NULL) + { + if (($params = $this->_get_params($params)) === FALSE) + { + return FALSE; + } + + if ($params['hmac'] !== FALSE) + { + if ( ! isset($params['hmac']['key'])) + { + $params['hmac']['key'] = $this->hkdf( + $params['key'], + $params['hmac']['digest'], + NULL, + NULL, + 'authentication' + ); + } + + // This might look illogical, but it is done during encryption as well ... + // The 'base64' value is effectively an inverted "raw data" parameter + $digest_size = ($params['base64']) + ? $this->_digests[$params['hmac']['digest']] * 2 + : $this->_digests[$params['hmac']['digest']]; + $hmac = substr($data, 0, $digest_size); + $data = substr($data, $digest_size); + + if ($hmac !== hash_hmac($params['hmac']['digest'], $data, $params['hmac']['key'], ! $params['base64'])) + { + return FALSE; + } + } + + if ($params['base64']) + { + $data = base64_decode($data); + } + + if ( ! isset($params['iv'])) + { + $iv_size = $this->{'_'.$this->_driver.'_get_iv_size'}($params['handle']); + if ($iv_size = $this->{'_'.$this->_driver.'_get_iv_size'}($params['handle'])) + { + $params['iv'] = substr($data, 0, $iv_size); + $data = substr($data, $iv_size); + } + else + { + $params['iv'] = NULL; + } + } + elseif (strncmp($params['iv'], $data, $iv_size = strlen($params['iv'])) === 0) + { + $data = substr($data, $iv_size); + } + + return $this->{'_'.$this->_driver.'_decrypt'}($data, $params); + } + + // -------------------------------------------------------------------- + + /** + * Decrypt via MCrypt + * + * @param string $data Encrypted data + * @param array $params Input parameters + * @return string + */ + protected function _mcrypt_decrypt($data, $params) + { + if ( ! is_resource($params['handle'])) + { + return FALSE; + } + elseif (mcrypt_generic_init($params['handle'], $params['key'], $params['iv']) < 0) + { + if ($params['handle'] !== $this->_handle) + { + mcrypt_module_close($params['handle']); + } + + return FALSE; + } + + $data = mdecrypt_generic($params['handle'], $data); + + mcrypt_generic_deinit($params['handle']); + if ($params['handle'] !== $this->_handle) + { + mcrypt_module_close($params['handle']); + } + + // Remove PKCS#7 padding + return substr($data, 0, -ord($data[strlen($data)-1])); + } + + // -------------------------------------------------------------------- + + /** + * Decrypt via OpenSSL + * + * @param string $data Encrypted data + * @param array $params Input parameters + * @return string + */ + protected function _openssl_decrypt($data, $params) + { + return empty($params['handle']) + ? FALSE + : openssl_decrypt( + $data, + $params['handle'], + $params['key'], + 1, // DO NOT TOUCH! + $params['iv'] + ); + } + + // -------------------------------------------------------------------- + + /** + * Get IV size via MCrypt + * + * @param resource $handle MCrypt module resource + * @return int + */ + protected function _mcrypt_get_iv_size($handle) + { + return mcrypt_enc_get_iv_size($handle); + } + + // -------------------------------------------------------------------- + + /** + * Get IV size via OpenSSL + * + * @param string $handle OpenSSL cipher method + * @return int + */ + protected function _openssl_get_iv_size($handle) + { + return openssl_cipher_iv_length($handle); + } + + // -------------------------------------------------------------------- + + /** + * Get IV via MCrypt + * + * @param int $size + * @return int + */ + protected function _mcrypt_get_iv($size) + { + // If /dev/urandom is available - use it, otherwise there's + // also /dev/random, but it is highly unlikely that it would + // be available while /dev/urandom is not and it is known to be + // blocking anyway. + if (defined(MCRYPT_DEV_URANDOM)) + { + $source = MCRYPT_DEV_URANDOM; + } + else + { + $source = MCRYPT_RAND; + is_php('5.3') OR srand(microtime(TRUE)); + } + + return mcrypt_create_iv($size, $source); + } + + // -------------------------------------------------------------------- + + /** + * Get IV via OpenSSL + * + * @param int $size IV size + * @return int + */ + protected function _openssl_get_iv($size) + { + return openssl_random_pseudo_bytes($size); + } + + // -------------------------------------------------------------------- + + /** + * Get params + * + * @param array $params Input parameters + * @return array + */ + protected function _get_params($params) + { + if (empty($params)) + { + return isset($this->_cipher, $this->_mode, $this->_key, $this->_handle) + ? array( + 'handle' => $this->_handle, + 'cipher' => $this->_cipher, + 'mode' => $this->_mode, + 'key' => $this->_key, + 'base64' => TRUE, + 'hmac' => $this->_mode === 'gcm' ? FALSE : array('digest' => 'sha512', 'key' => NULL) + ) + : FALSE; + } + elseif ( ! isset($params['cipher'], $params['mode'], $params['key'])) + { + return FALSE; + } + + if ($params['mode'] === 'gcm') + { + $params['hmac'] = FALSE; + } + elseif ( ! isset($params['hmac']) OR ( ! is_array($params['hmac']) && $params['hmac'] !== FALSE)) + { + $params['hmac'] = array( + 'digest' => 'sha512', + 'key' => NULL + ); + } + elseif (is_array($params['hmac'])) + { + if (isset($params['hmac']['digest']) && ! isset($this->_digests[$params['hmac']['digest']])) + { + return FALSE; + } + + $params['hmac'] = array( + 'digest' => isset($params['hmac']['digest']) ? $params['hmac']['digest'] : 'sha512', + 'key' => isset($params['hmac']['key']) ? $params['hmac']['key'] : NULL + ); + } + + $params = array( + 'handle' => NULL, + 'cipher' => isset($params['cipher']) ? $params['cipher'] : $this->_cipher, + 'mode' => isset($params['mode']) ? $params['mode'] : $this->_mode, + 'key' => isset($params['key']) ? $params['key'] : $this->_key, + 'iv' => isset($params['iv']) ? $params['iv'] : NULL, + 'base64' => isset($params['base64']) ? $params['base64'] : TRUE, + 'hmac' => $params['hmac'] + ); + + $this->_cipher_alias($params['cipher']); + $params['handle'] = ($params['cipher'] !== $this->_cipher OR $params['mode'] !== $this->_mode) + ? $this->{'_'.$this->_driver.'_get_handle'}($params['cipher'], $params['mode']) + : $this->_handle; + + return $params; + } + + // -------------------------------------------------------------------- + + /** + * Get MCrypt handle + * + * @param string $cipher Cipher name + * @param string $mode Encryption mode + * @return resource + */ + protected function _mcrypt_get_handle($cipher, $mode) + { + return mcrypt_module_open($cipher, '', $mode, ''); + } + + // -------------------------------------------------------------------- + + /** + * Get OpenSSL handle + * + * @param string $cipher Cipher name + * @param string $mode Encryption mode + * @return string + */ + protected function _openssl_get_handle($cipher, $mode) + { + // OpenSSL methods aren't suffixed with '-stream' for this mode + return ($mode === 'stream') + ? $cipher + : $cipher.'-'.$mode; + } + + // -------------------------------------------------------------------- + + /** + * Cipher alias + * + * Tries to translate cipher names between MCrypt and OpenSSL's "dialects". + * + * @param string $cipher Cipher name + * @return void + */ + protected function _cipher_alias(&$cipher) + { + static $dictionary; + + if (empty($dictionary)) + { + $dictionary = array( + 'mcrypt' => array( + 'aes-128' => 'rijndael-128', + 'aes-192' => 'rijndael-128', + 'aes-256' => 'rijndael-128', + 'des3-ede3' => 'tripledes' + ), + 'openssl' => array( + 'rijndael-128' => 'aes-128', + 'tripledes' => 'des-ede3' + ) + ); + + // Notes regarding other seemingly matching ciphers between + // MCrypt and OpenSSL: + // + // - DES is compatible, but doesn't need an alias + // - Blowfish is NOT compatible + // mcrypt: 'blowfish', 'blowfish-compat' + // openssl: 'bf' + // - CAST-128/CAST5 is NOT compatible + // mcrypt: 'cast-128' + // openssl: 'cast5' + // - RC2 is NOT compatible + // mcrypt: 'rc2' + // openssl: 'rc2', 'rc2-40', 'rc2-64' + // + // To avoid any other confusion due to a popular (but incorrect) + // belief, it should also be noted that Rijndael-192/256 are NOT + // the same ciphers as AES-192/256 like Rijndael-128 and AES-256 is. + // + // All compatibility tests were done in CBC mode. + } + + if (isset($dictionary[$this->_driver][$cipher])) + { + $cipher = $dictionary[$this->_driver][$cipher]; + } + } + + // -------------------------------------------------------------------- + + /** + * HKDF + * + * @link https://tools.ietf.org/rfc/rfc5869.txt + * @param $key Input key + * @param $digest A SHA-2 hashing algorithm + * @param $salt Optional salt + * @param $info Optional context/application-specific info + * @param $length Output length (defaults to the selected digest size) + * @return string A pseudo-random key + */ + public function hkdf($key, $digest = 'sha512', $salt = NULL, $length = NULL, $info = '') + { + if ( ! isset($this->_digests[$digest])) + { + return FALSE; + } + + if (empty($length) OR ! is_int($length)) + { + $length = $this->_digests[$digest]; + } + elseif ($length > (255 * $this->_digests[$digest])) + { + return FALSE; + } + + isset($salt) OR $salt = str_repeat("\0", $this->_digests[$digest]); + + $prk = hash_hmac($digest, $key, $salt, TRUE); + $key = ''; + for ($key_block = '', $block_index = 1; strlen($key) < $length; $block_index++) + { + $key_block = hash_hmac($digest, $key_block.$info.chr($block_index), $prk, TRUE); + $key .= $key_block; + } + + return substr($key, 0, $length); + } + + // -------------------------------------------------------------------- + + /** + * __get() magic + * + * @param string $key Property name + * @return mixed + */ + public function __get($key) + { + return in_array($key, array('cipher', 'mode', 'driver', 'drivers', 'digests'), TRUE) + ? $this->{'_'.$key} + : NULL; + } + +} + +/* End of file Encryption.php */ +/* Location: ./system/libraries/Encryption.php */
\ No newline at end of file diff --git a/system/libraries/Session/drivers/Session_cookie.php b/system/libraries/Session/drivers/Session_cookie.php index 971dfeabe..5d338fc04 100644 --- a/system/libraries/Session/drivers/Session_cookie.php +++ b/system/libraries/Session/drivers/Session_cookie.php @@ -240,7 +240,7 @@ class CI_Session_cookie extends CI_Session_driver { // Do we need encryption? If so, load the encryption class if ($this->sess_encrypt_cookie === TRUE) { - $this->CI->load->library('encrypt'); + $this->CI->load->library('encryption'); } // Check for database @@ -383,30 +383,33 @@ class CI_Session_cookie extends CI_Session_driver { return FALSE; } - $len = strlen($session) - 40; - - if ($len < 0) + if ($this->sess_encrypt_cookie === TRUE) { - log_message('debug', 'The session cookie was not signed.'); - return FALSE; + $session = $this->CI->encryption->decrypt($session); + if ($session === FALSE) + { + log_message('error', 'Session: Unable to decrypt the session cookie, possibly due to a HMAC mismatch.'); + return FALSE; + } } - - // Check cookie authentication - $hmac = substr($session, $len); - $session = substr($session, 0, $len); - - if ($hmac !== hash_hmac('sha1', $session, $this->encryption_key)) + else { - log_message('error', 'The session cookie data did not match what was expected.'); - $this->sess_destroy(); - return FALSE; - } + if (($len = strlen($session) - 40) <= 0) + { + log_message('error', 'Session: The session cookie was not signed.'); + return FALSE; + } - // Check for encryption - if ($this->sess_encrypt_cookie === TRUE) - { - // Decrypt the cookie data - $session = $this->CI->encrypt->decode($session); + // Check cookie authentication + $hmac = substr($session, $len); + $session = substr($session, 0, $len); + + if ($hmac !== hash_hmac('sha1', $session, $this->encryption_key)) + { + log_message('error', 'Session: HMAC mismatch. The session cookie data did not match what was expected.'); + $this->sess_destroy(); + return FALSE; + } } // Unserialize the session array @@ -723,11 +726,13 @@ class CI_Session_cookie extends CI_Session_driver { if ($this->sess_encrypt_cookie === TRUE) { - $cookie_data = $this->CI->encrypt->encode($cookie_data); + $cookie_data = $this->CI->encryption->encrypt($cookie_data); + } + else + { + // Require message authentication + $cookie_data .= hash_hmac('sha1', $cookie_data, $this->encryption_key); } - - // Require message authentication - $cookie_data .= hash_hmac('sha1', $cookie_data, $this->encryption_key); $expire = ($this->sess_expire_on_close === TRUE) ? 0 : $this->sess_expiration + time(); diff --git a/tests/codeigniter/libraries/Encryption_test.php b/tests/codeigniter/libraries/Encryption_test.php new file mode 100644 index 000000000..3d091e8d8 --- /dev/null +++ b/tests/codeigniter/libraries/Encryption_test.php @@ -0,0 +1,130 @@ +<?php + +class Encryption_test extends CI_TestCase { + + public function set_up() + { + $this->ci_set_config('encryption_key', "\xd0\xc9\x08\xc4\xde\x52\x12\x6e\xf8\xcc\xdb\x03\xea\xa0\x3a\x5c"); + $this->encryption = new CI_Encryption(); + $this->ci_instance_var('encryption', $this->encryption); + } + + // -------------------------------------------------------------------- + + public function test_portability() + { + if ( ! $this->encryption->drivers['mcrypt'] OR ! $this->encryption->drivers['openssl']) + { + $this->markTestAsSkipped('Both MCrypt and OpenSSL support are required for portability tests.'); + return; + } + + $message = 'This is a message encrypted via MCrypt and decrypted via OpenSSL, or vice-versa.'; + + // As it turns out, only ciphers that happened to be a US standard have a + // somewhat consistent implementation between MCrypt and OpenSSL, so + // we can only test AES, DES and TripleDES. + // + // Format is: <MCrypt cipher name>, <OpenSSL cipher name>, <key size> + $portable = array( + array('rijndael-128', 'aes-128', 16), + array('rijndael-128', 'aes-192', 24), + array('rijndael-128', 'aes-256', 32), + array('des', 'des', 7), + array('tripledes', 'des-ede3', 7), + array('tripledes', 'des-ede3', 14), + array('tripledes', 'des-ede3', 21) + ); + $driver_index = array('mcrypt', 'openssl'); + + foreach ($portable as &$test) + { + // Add some randomness to the selected driver + $driver = mt_rand(0,1); + $params = array( + 'cipher' => $test[$driver], + 'mode' => 'cbc', + 'key' => openssl_random_pseudo_bytes($test[2]) + ); + + $this->encryption->initialize(array('driver' => $driver_index[$driver])); + $ciphertext = $this->encryption->encrypt($message, $params); + + $driver = (int) ! $driver; + $params['cipher'] = $test[$driver]; + + $this->encryption->initialize(array('driver' => $driver_index[$driver])); + $this->assertEquals($message, $this->encryption->decrypt($ciphertext, $params)); + } + } + + // -------------------------------------------------------------------- + + public function test_hkdf() + { + // Test vectors are described in RFC5869, Appendix A(1-3). + // Vectors 4-7 cover SHA-1, which we don't support. + // + // URL: https://tools.ietf.org/rfc/rfc5869.txt + // + // Our implementation doesn't split into hkdf_extract(), hkdf_expand() + // and therefore we can't test for the PRK value (it is included below + // just for consistency, hence why it's also commented out). + // + // As long as OKM is correct, then we're all fine though. + $vectors = array( + // Appendix A.1: Basic test case with SHA-256 + array( + 'digest' => 'sha256', + 'ikm' => "\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b", + 'salt' => "\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c", + 'length' => 42, + 'info' => "\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9", +// 'prk' => "\x07\x77\x09\x36\x2c\x2e\x32\xdf\x0d\xdc\x3f\x0d\xc4\x7b\xba\x63\x90\xb6\xc7\x3b\xb5\x0f\x9c\x31\x22\xec\x84\x4a\xd7\xc2\xb3\xe5", + 'okm' => "\x3c\xb2\x5f\x25\xfa\xac\xd5\x7a\x90\x43\x4f\x64\xd0\x36\x2f\x2a\x2d\x2d\x0a\x90\xcf\x1a\x5a\x4c\x5d\xb0\x2d\x56\xec\xc4\xc5\xbf\x34\x00\x72\x08\xd5\xb8\x87\x18\x58\x65" + ), + // Appendix A.2: Test with SHA-256 and longer inputs/outputs + array( + 'digest' => 'sha256', + 'ikm' => "\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x20\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39\x3a\x3b\x3c\x3d\x3e\x3f\x40\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x4b\x4c\x4d\x4e\x4f", + 'salt' => "\x60\x61\x62\x63\x64\x65\x66\x67\x68\x69\x6a\x6b\x6c\x6d\x6e\x6f\x70\x71\x72\x73\x74\x75\x76\x77\x78\x79\x7a\x7b\x7c\x7d\x7e\x7f\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf", + 'length' => 82, + 'info' => "\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff", +// 'prk' => "\x06\xa6\xb8\x8c\x58\x53\x36\x1a\x06\x10\x4c\x9c\xeb\x35\xb4\x5c\xef\x76\x00\x14\x90\x46\x71\x01\x4a\x19\x3f\x40\xc1\x5f\xc2\x44", + 'okm' => "\xb1\x1e\x39\x8d\xc8\x03\x27\xa1\xc8\xe7\xf7\x8c\x59\x6a\x49\x34\x4f\x01\x2e\xda\x2d\x4e\xfa\xd8\xa0\x50\xcc\x4c\x19\xaf\xa9\x7c\x59\x04\x5a\x99\xca\xc7\x82\x72\x71\xcb\x41\xc6\x5e\x59\x0e\x09\xda\x32\x75\x60\x0c\x2f\x09\xb8\x36\x77\x93\xa9\xac\xa3\xdb\x71\xcc\x30\xc5\x81\x79\xec\x3e\x87\xc1\x4c\x01\xd5\xc1\xf3\x43\x4f\x1d\x87", + ), + // Appendix A.3: Test with SHA-256 and zero-length salt/info + array( + 'digest' => 'sha256', + 'ikm' => "\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b", + 'salt' => "", + 'length' => 42, + 'info' => "", +// 'prk' => "\x19\xef\x24\xa3\x2c\x71\x7b\x16\x7f\x33\xa9\x1d\x6f\x64\x8b\xdf\x96\x59\x67\x76\xaf\xdb\x63\x77\xac\x43\x4c\x1c\x29\x3c\xcb\x04", + 'okm' => "\x8d\xa4\xe7\x75\xa5\x63\xc1\x8f\x71\x5f\x80\x2a\x06\x3c\x5a\x31\xb8\xa1\x1f\x5c\x5e\xe1\x87\x9e\xc3\x45\x4e\x5f\x3c\x73\x8d\x2d\x9d\x20\x13\x95\xfa\xa4\xb6\x1a\x96\xc8", + ) + ); + + foreach ($vectors as $test) + { + $this->assertEquals( + $test['okm'], + $this->encryption->hkdf( + $test['ikm'], + $test['digest'], + $test['salt'], + $test['length'], + $test['info'] + ) + ); + } + + // Test default length, it must match the digest size + $this->assertEquals(64, strlen($this->encryption->hkdf('foobar', 'sha512'))); + + // Test maximum length (RFC5869 says that it must be up to 255 times the digest size) + $this->assertEquals(12240, strlen($this->encryption->hkdf('foobar', 'sha384', NULL, 48 * 255))); + $this->assertFalse($this->encryption->hkdf('foobar', 'sha224', NULL, 28 * 255 + 1)); + } + +}
\ No newline at end of file diff --git a/tests/mocks/autoloader.php b/tests/mocks/autoloader.php index cc0a2e2f7..1bcde797d 100644 --- a/tests/mocks/autoloader.php +++ b/tests/mocks/autoloader.php @@ -14,26 +14,49 @@ function autoload($class) $dir = realpath(dirname(__FILE__)).DIRECTORY_SEPARATOR; $ci_core = array( - 'Benchmark', 'Config', 'Controller', - 'Exceptions', 'Hooks', 'Input', - 'Lang', 'Loader', 'Log', 'Model', - 'Output', 'Router', 'Security', - 'URI', 'Utf8', + 'Benchmark', + 'Config', + 'Controller', + 'Exceptions', + 'Hooks', + 'Input', + 'Lang', + 'Loader', + 'Log', + 'Model', + 'Output', + 'Router', + 'Security', + 'URI', + 'Utf8' ); $ci_libraries = array( - 'Calendar', 'Cart', 'Driver_Library', - 'Email', 'Encrypt', 'Form_validation', - 'Ftp', 'Image_lib', 'Javascript', - 'Migration', 'Pagination', 'Parser', - 'Profiler', 'Table', 'Trackback', - 'Typography', 'Unit_test', 'Upload', - 'User_agent', 'Xmlrpc', 'Zip' + 'Calendar', + 'Cart', + 'Driver_Library', + 'Email', + 'Encrypt', + 'Encryption', + 'Form_validation', + 'Ftp', + 'Image_lib', + 'Javascript', + 'Migration', + 'Pagination', + 'Parser', + 'Profiler', + 'Table', + 'Trackback', + 'Typography', + 'Unit_test', + 'Upload', + 'User_agent', + 'Xmlrpc', + 'Zip' ); - $ci_drivers = array( - 'Session', - ); + $ci_drivers = array('Session', 'Cache'); if (strpos($class, 'Mock_') === 0) { diff --git a/user_guide_src/source/changelog.rst b/user_guide_src/source/changelog.rst index c6d42d923..962c174a9 100644 --- a/user_guide_src/source/changelog.rst +++ b/user_guide_src/source/changelog.rst @@ -257,6 +257,14 @@ Release Date: Not Released - Libraries + - Added a new :doc:`Encryption Library <libraries/encryption>` to replace the old, largely insecure :doc:`Encrypt Library <libraries/encrypt>`. + + - :doc:`Encrypt Library <libraries/encrypt>` changes include: + + - Deprecated the library in favor of the new :doc:`Encryption Library <libraries/encryption>`. + - Added support for hashing algorithms other than SHA1 and MD5. + - Removed previously deprecated ``sha1()`` method. + - :doc:`Session Library <libraries/sessions>` changes include: - Library changed to :doc:`Driver <general/drivers>` with classic 'cookie' driver as the default. @@ -358,11 +366,6 @@ Release Date: Not Released - Added $config['reuse_query_string'] to allow automatic repopulation of query string arguments, combined with normal URI segments. - Removed the default `` `` from a number of the configuration variables. - - :doc:`Encryption Library <libraries/encryption>` changes include: - - - Added support for hashing algorithms other than SHA1 and MD5. - - Removed previously deprecated ``sha1()`` method. - - :doc:`Profiler Library <general/profiling>` changes include: - Database object names are now being displayed. @@ -574,7 +577,7 @@ Bug fixes for 3.0 - Fixed a bug (#1264) - :doc:`Database Forge <database/forge>` and :doc:`Database Utilities <database/utilities>` didn't update/reset the databases and tables list cache when a table or a database is created, dropped or renamed. - Fixed a bug (#7) - :doc:`Query Builder <database/query_builder>`'s ``join()`` method only escaped one set of conditions. - Fixed a bug (#1321) - Core Exceptions class couldn't find the errors/ folder in some cases. -- Fixed a bug (#1202) - :doc:`Encryption Library <libraries/encryption>` encode_from_legacy() didn't set back the encrypt mode on failure. +- Fixed a bug (#1202) - :doc:`Encrypt Library <libraries/encrypt>` method ``encode_from_legacy()`` didn't set back the encrypt mode on failure. - Fixed a bug (#145) - compile_binds() failed when the bind marker was present in a literal string within the query. - Fixed a bug in protect_identifiers() where if passed along with the field names, operators got escaped as well. - Fixed a bug (#10) - :doc:`URI Library <libraries/uri>` internal method _detect_uri() failed with paths containing a colon. @@ -1289,7 +1292,7 @@ Hg Tag: v2.0.0 - Documented append_output() in the :doc:`Output Class <libraries/output>`. - Documented a second argument in the decode() function for the - :doc:`Encryption Class <libraries/encryption>`. + :doc:`Encryption Class <libraries/encrypt>`. - Documented db->close(). - Updated the router to support a default route with any number of segments. diff --git a/user_guide_src/source/installation/upgrade_200.rst b/user_guide_src/source/installation/upgrade_200.rst index 29f44bd9e..948b1bc58 100644 --- a/user_guide_src/source/installation/upgrade_200.rst +++ b/user_guide_src/source/installation/upgrade_200.rst @@ -50,11 +50,11 @@ to :: Step 4: Update stored encrypted data ==================================== -.. note:: If your application does not use the Encryption library, does +.. note:: If your application does not use the Encrypt library, does not store Encrypted data permanently, or is on an environment that does not support Mcrypt, you may skip this step. -The Encryption library has had a number of improvements, some for +The Encrypt library has had a number of improvements, some for encryption strength and some for performance, that has an unavoidable consequence of making it no longer possible to decode encrypted data produced by the original version of this library. To help with the @@ -65,7 +65,7 @@ replace stale encrypted data with fresh in your applications, either on the fly or en masse. Please read `how to use this -method <../libraries/encryption.html#legacy>`_ in the Encryption library +method <../libraries/encrypt.html#legacy>`_ in the Encrypt library documentation. Step 5: Remove loading calls for the compatibility helper. diff --git a/user_guide_src/source/installation/upgrade_300.rst b/user_guide_src/source/installation/upgrade_300.rst index 88bb11178..94385978e 100644 --- a/user_guide_src/source/installation/upgrade_300.rst +++ b/user_guide_src/source/installation/upgrade_300.rst @@ -318,7 +318,7 @@ The SHA1 library The previously deprecated SHA1 library has been removed, alter your code to use PHP's native ``sha1()`` function to generate a SHA1 hash. -Additionally, the ``sha1()`` method in the :doc:`Encryption Library <../libraries/encryption>` has been removed. +Additionally, the ``sha1()`` method in the :doc:`Encrypt Library <../libraries/encrypt>` has been removed. The EXT constant ================ @@ -333,6 +333,23 @@ Smiley helper js_insert_smiley() :doc:`Smiley Helper <../helpers/smiley_helper>` function ``js_insert_smiley()`` has been deprecated since CodeIgniter 1.7.2 and is now removed. You'll need to switch to ``smiley_js()`` instead. +The Encrypt library +=================== + +Following numerous vulnerability reports, the :doc:`Encrypt Library <../libraries/encrypt>` has +been deprecated and a new, :doc:`Encryption Library <../libraries/encryption>` is added to take +its place. + +The new library requires either the `MCrypt extension <http://php.net/mcrypt>`_ or PHP 5.3.3 and +the `OpenSSL extension <http://php.net/openssl>`_. While this might be rather inconvenient, it is +a requirement that allows us to have properly implemented cryptographic functions. + +.. note:: The :doc:`Encrypt Library <../libraries/encrypt>` is still available for the purpose + of keeping backwards compatibility. + +.. important:: You are strongly encouraged to switch to the new :doc:`Encryption Library + <../libraries/encryption>` as soon as possible! + Database drivers 'mysql', 'sqlite', 'mssql', 'pdo/dblib' ======================================================== diff --git a/user_guide_src/source/libraries/encryption.rst b/user_guide_src/source/libraries/encrypt.rst index a38122203..a38122203 100644 --- a/user_guide_src/source/libraries/encryption.rst +++ b/user_guide_src/source/libraries/encrypt.rst |