From 818aedb6ca46ee8498624d49403e5ff1404bc692 Mon Sep 17 00:00:00 2001 From: Andrey Andreev Date: Mon, 3 Feb 2014 11:30:25 +0200 Subject: Introducing CI_Encryption (a CI_Encrypt replacement) --- system/libraries/Encryption.php | 718 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 718 insertions(+) create mode 100644 system/libraries/Encryption.php (limited to 'system') diff --git a/system/libraries/Encryption.php b/system/libraries/Encryption.php new file mode 100644 index 000000000..76569f281 --- /dev/null +++ b/system/libraries/Encryption.php @@ -0,0 +1,718 @@ +_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.'); + } + + $this->initialize($params); + + if (empty($this->_driver)) + { + $this->_driver = ($this->_drivers['mcrypt'] === TRUE) + ? 'mcrypt' + : 'openssl'; + + log_message('debug', "Encryption: Auto-configured driver '".$params['driver']."'."); + } + + 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->_driver[$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."); + } + } + + 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->{'_'.$this->_driver.'_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; + } + + return ($params['base64']) + ? base64_encode($data) + : $data; + } + + // -------------------------------------------------------------------- + + /** + * Encrypt via MCrypt + * + * @param string $data Input data + * @param array $params Input parameters + * @return string + */ + protected function _mcrypt_encrypt($data, $params) + { + if (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($handle); + } + + return $data; + } + + // -------------------------------------------------------------------- + + /** + * Encrypt via OpenSSL + * + * @param string $data Input data + * @param array $params Input parameters + * @return string + */ + protected function _openssl_encrypt($data, $params) + { + $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->{'_'.$this->_driver.'_get_params'}($params)) === FALSE) + { + return FALSE; + } + elseif ($params['base64']) + { + $data = base64_decode($data); + } + + if ( ! isset($params['iv'])) + { + if ($iv_size) + { + $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 (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($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 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 MCrypt parameters + * + * @param array $params Input parameters + * @return array + */ + protected function _mcrypt_get_params($params) + { + if (empty($params)) + { + if ( ! isset($this->_cipher, $this->_mode, $params->_key, $this->_handle) OR ! is_resource($this->_handle)) + { + return FALSE; + } + + return array( + 'handle' => $this->_handle, + 'cipher' => $this->_cipher, + 'mode' => $this->_mode, + 'key' => $this->_key, + 'base64' => TRUE + ); + } + + $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 + ); + + if ( ! isset($params['cipher'], $params['mode'], $params['key'])) + { + return FALSE; + } + + $this->_cipher_alias($params['cipher']); + + if ($params['cipher'] !== $this->_cipher OR $params['mode'] !== $this->_mode) + { + if (($params['handle'] = mcrypt_module_open($params['cipher'], '', $params['mode'], '')) === FALSE) + { + return FALSE; + } + } + else + { + if ( ! is_resource($this->_handle)) + { + return FALSE; + } + + $params['handle'] = $this->_handle; + } + + return $params; + } + + // -------------------------------------------------------------------- + + /** + * Get OpenSSL parameters + * + * @param array $params Input parameters + * @return array + */ + protected function _openssl_get_params($params) + { + if (empty($params)) + { + if ( ! isset($this->_cipher, $this->_mode, $params->_key, $this->_handle)) + { + return FALSE; + } + + return array( + 'handle' => $this->_handle, + 'cipher' => $this->_cipher, + 'mode' => $this->_mode, + 'key' => $this->_key, + 'base64' => TRUE + ); + } + + $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 + ); + + if ( ! isset($params['cipher'], $params['mode'], $params['key'])) + { + return FALSE; + } + + $this->_cipher_alias($params['cipher']); + + if ($params['cipher'] !== $this->_cipher OR $params['mode'] !== $this->_mode) + { + // OpenSSL methods aren't suffixed with '-stream' for this mode + $params['handle'] = ($params['mode'] === 'stream') + ? $params['cipher'] + : $params['cipher'].'-'.$params['mode']; + $params['handle'] = $params['cipher'].'-'.$params['mode']; + } + else + { + if (empty($this->_handle)) + { + return FALSE; + } + + $params['handle'] = $this->_handle; + } + + return $params; + } + + // -------------------------------------------------------------------- + + /** + * 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( + 'rijndael-128', + 'tripledes', + 'arcfour' + ), + 'openssl' => array( + 'aes-128', + 'des-ede3', + 'rc4-40' + ) + ); + + // 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. + } + + $dialect = ($this->_driver === 'mcrypt') + ? 'openssl' + : 'mcrypt'; + if (($index = array_search($cipher, $dictionary[$dialect], TRUE)) !== FALSE) + { + $cipher = $dictionary[$this->_driver][$index]; + } + } + + // -------------------------------------------------------------------- + + /** + * __get() magic + * + * @param string $key Property name + * @return mixed + */ + public function __get($key) + { + return in_array($key, array('cipher', 'mode', 'driver', 'drivers'), TRUE) + ? $this->{'_'.$key} + : NULL; + } + +} + +/* End of file Encryption.php */ +/* Location: ./system/libraries/Encryption.php */ \ No newline at end of file -- cgit v1.2.3-24-g4f1b