| <?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); |
| |
| 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->_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($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 a "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'])) |
| { |
| 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 ( ! 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($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, $params->_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( |
| '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]; |
| } |
| } |
| |
| // -------------------------------------------------------------------- |
| |
| /** |
| * 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'), TRUE) |
| ? $this->{'_'.$key} |
| : NULL; |
| } |
| |
| } |
| |
| /* End of file Encryption.php */ |
| /* Location: ./system/libraries/Encryption.php */ |