Revision 1257 | Zur aktuellen Revision | Blame | Vergleich mit vorheriger | Letzte Änderung | Log anzeigen | RSS feed
<?php/*** CodeIgniter** An open source application development framework for PHP** This content is released under the MIT License (MIT)** Copyright (c) 2014 - 2016, British Columbia Institute of Technology** Permission is hereby granted, free of charge, to any person obtaining a copy* of this software and associated documentation files (the "Software"), to deal* in the Software without restriction, including without limitation the rights* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell* copies of the Software, and to permit persons to whom the Software is* furnished to do so, subject to the following conditions:** The above copyright notice and this permission notice shall be included in* all copies or substantial portions of the Software.** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN* THE SOFTWARE.** @package CodeIgniter* @author EllisLab Dev Team* @copyright Copyright (c) 2008 - 2014, EllisLab, Inc. (https://ellislab.com/)* @copyright Copyright (c) 2014 - 2016, British Columbia Institute of Technology (http://bcit.ca/)* @license http://opensource.org/licenses/MIT MIT License* @link https://codeigniter.com* @since Version 3.0.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 https://codeigniter.com/user_guide/libraries/encryption.html*/class CI_Encryption {/*** Encryption cipher** @var string*/protected $_cipher = 'aes-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 available modes** @var array*/protected $_modes = array('mcrypt' => array('cbc' => 'cbc','ecb' => 'ecb','ofb' => 'nofb','ofb8' => 'ofb','cfb' => 'ncfb','cfb8' => 'cfb','ctr' => 'ctr','stream' => 'stream'),'openssl' => array('cbc' => 'cbc','ecb' => 'ecb','ofb' => 'ofb','cfb' => 'cfb','cfb8' => 'cfb8','ctr' => 'ctr','stream' => '','xts' => 'xts'));/*** List of supported HMAC algorithms** name => digest size pairs** @var array*/protected $_digests = array('sha224' => 28,'sha256' => 32,'sha384' => 48,'sha512' => 64);/*** mbstring.func_override flag** @var bool*/protected static $func_override;// --------------------------------------------------------------------/*** Class constructor** @param array $params Configuration parameters* @return void*/public function __construct(array $params = array()){$this->_drivers = array('mcrypt' => defined('MCRYPT_DEV_URANDOM'),// 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']){show_error('Encryption: Unable to find an available encryption driver.');}isset(self::$func_override) OR self::$func_override = (extension_loaded('mbstring') && ini_get('mbstring.func_override'));$this->initialize($params);if ( ! isset($this->_key) && self::strlen($key = config_item('encryption_key')) > 0){$this->_key = $key;}log_message('info', '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['openssl'] === TRUE)? 'openssl': 'mcrypt';log_message('debug', "Encryption: Auto-configured driver '".$this->_driver."'.");}empty($params['cipher']) && $params['cipher'] = $this->_cipher;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'])){$params['mode'] = strtolower($params['mode']);if ( ! isset($this->_modes['mcrypt'][$params['mode']])){log_message('error', 'Encryption: MCrypt mode '.strtoupper($params['mode']).' is not available.');}else{$this->_mode = $this->_modes['mcrypt'][$params['mode']];}}if (isset($this->_cipher, $this->_mode)){if (is_resource($this->_handle)&& (strtolower(mcrypt_enc_get_algorithms_name($this->_handle)) !== $this->_cipherOR 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('info', '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'])){$params['mode'] = strtolower($params['mode']);if ( ! isset($this->_modes['openssl'][$params['mode']])){log_message('error', 'Encryption: OpenSSL mode '.strtoupper($params['mode']).' is not available.');}else{$this->_mode = $this->_modes['openssl'][$params['mode']];}}if (isset($this->_cipher, $this->_mode)){// This is mostly for the stream mode, which doesn't get suffixed in OpenSSL$handle = empty($this->_mode)? $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('info', 'Encryption: OpenSSL initialized with method '.strtoupper($handle).'.');}}}// --------------------------------------------------------------------/*** Create a random key** @param int $length Output length* @return string*/public function create_key($length){if (function_exists('random_bytes')){try{return random_bytes((int) $length);}catch (Exception $e){log_message('error', $e->getMessage());return FALSE;}}elseif (defined('MCRYPT_DEV_URANDOM')){return mcrypt_create_iv($length, MCRYPT_DEV_URANDOM);}$is_secure = NULL;$key = openssl_random_pseudo_bytes($length, $is_secure);return ($is_secure === TRUE)? $key: FALSE;}// --------------------------------------------------------------------/*** 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;}isset($params['key']) OR $params['key'] = $this->hkdf($this->_key, 'sha512', NULL, self::strlen($this->_key), 'encryption');if (($data = $this->{'_'.$this->_driver.'_encrypt'}($data, $params)) === FALSE){return FALSE;}$params['base64'] && $data = base64_encode($data);if (isset($params['hmac_digest'])){isset($params['hmac_key']) OR $params['hmac_key'] = $this->hkdf($this->_key, 'sha512', 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;}// The greater-than-1 comparison is mostly a work-around for a bug,// where 1 is returned for ARCFour instead of 0.$iv = (($iv_size = mcrypt_enc_get_iv_size($params['handle'])) > 1)? $this->create_key($iv_size): NULL;if (mcrypt_generic_init($params['handle'], $params['key'], $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.if (in_array(strtolower(mcrypt_enc_get_modes_name($params['handle'])), array('cbc', 'ecb'), TRUE)){$block_size = mcrypt_enc_get_block_size($params['handle']);$pad = $block_size - (self::strlen($data) % $block_size);$data .= str_repeat(chr($pad), $pad);}// Work-around for yet another strange behavior in MCrypt.//// When encrypting in ECB mode, the IV is ignored. Yet// mcrypt_enc_get_iv_size() returns a value larger than 0// even if ECB is used AND mcrypt_generic_init() complains// if you don't pass an IV with length equal to the said// return value.//// This probably would've been fine (even though still wasteful),// but OpenSSL isn't that dumb and we need to make the process// portable, so ...$data = (mcrypt_enc_get_modes_name($params['handle']) !== 'ECB')? $iv.mcrypt_generic($params['handle'], $data): 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;}$iv = ($iv_size = openssl_cipher_iv_length($params['handle']))? $this->create_key($iv_size): NULL;$data = openssl_encrypt($data,$params['handle'],$params['key'],1, // DO NOT TOUCH!$iv);if ($data === FALSE){return FALSE;}return $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 (isset($params['hmac_digest'])){// 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']];if (self::strlen($data) <= $digest_size){return FALSE;}$hmac_input = self::substr($data, 0, $digest_size);$data = self::substr($data, $digest_size);isset($params['hmac_key']) OR $params['hmac_key'] = $this->hkdf($this->_key, 'sha512', NULL, NULL, 'authentication');$hmac_check = hash_hmac($params['hmac_digest'], $data, $params['hmac_key'], ! $params['base64']);// Time-attack-safe comparison$diff = 0;for ($i = 0; $i < $digest_size; $i++){$diff |= ord($hmac_input[$i]) ^ ord($hmac_check[$i]);}if ($diff !== 0){return FALSE;}}if ($params['base64']){$data = base64_decode($data);}isset($params['key']) OR $params['key'] = $this->hkdf($this->_key, 'sha512', NULL, self::strlen($this->_key), 'encryption');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;}// The greater-than-1 comparison is mostly a work-around for a bug,// where 1 is returned for ARCFour instead of 0.if (($iv_size = mcrypt_enc_get_iv_size($params['handle'])) > 1){if (mcrypt_enc_get_modes_name($params['handle']) !== 'ECB'){$iv = self::substr($data, 0, $iv_size);$data = self::substr($data, $iv_size);}else{// MCrypt is dumb and this is ignored, only size matters$iv = str_repeat("\x0", $iv_size);}}else{$iv = NULL;}if (mcrypt_generic_init($params['handle'], $params['key'], $iv) < 0){if ($params['handle'] !== $this->_handle){mcrypt_module_close($params['handle']);}return FALSE;}$data = mdecrypt_generic($params['handle'], $data);// Remove PKCS#7 padding, if necessaryif (in_array(strtolower(mcrypt_enc_get_modes_name($params['handle'])), array('cbc', 'ecb'), TRUE)){$data = self::substr($data, 0, -ord($data[self::strlen($data)-1]));}mcrypt_generic_deinit($params['handle']);if ($params['handle'] !== $this->_handle){mcrypt_module_close($params['handle']);}return $data;}// --------------------------------------------------------------------/*** Decrypt via OpenSSL** @param string $data Encrypted data* @param array $params Input parameters* @return string*/protected function _openssl_decrypt($data, $params){if ($iv_size = openssl_cipher_iv_length($params['handle'])){$iv = self::substr($data, 0, $iv_size);$data = self::substr($data, $iv_size);}else{$iv = NULL;}return empty($params['handle'])? FALSE: openssl_decrypt($data,$params['handle'],$params['key'],1, // DO NOT TOUCH!$iv);}// --------------------------------------------------------------------/*** 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' => NULL,'base64' => TRUE,'hmac_digest' => 'sha512','hmac_key' => NULL): FALSE;}elseif ( ! isset($params['cipher'], $params['mode'], $params['key'])){return FALSE;}if (isset($params['mode'])){$params['mode'] = strtolower($params['mode']);if ( ! isset($this->_modes[$this->_driver][$params['mode']])){return FALSE;}else{$params['mode'] = $this->_modes[$this->_driver][$params['mode']];}}if (isset($params['hmac']) && $params['hmac'] === FALSE){$params['hmac_digest'] = $params['hmac_key'] = NULL;}else{if ( ! isset($params['hmac_key'])){return FALSE;}elseif (isset($params['hmac_digest'])){$params['hmac_digest'] = strtolower($params['hmac_digest']);if ( ! isset($this->_digests[$params['hmac_digest']])){return FALSE;}}else{$params['hmac_digest'] = 'sha512';}}$params = array('handle' => NULL,'cipher' => $params['cipher'],'mode' => $params['mode'],'key' => $params['key'],'base64' => isset($params['raw_data']) ? ! $params['raw_data'] : FALSE,'hmac_digest' => $params['hmac_digest'],'hmac_key' => $params['hmac_key']);$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 modereturn ($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','bf' => 'blowfish','cast5' => 'cast-128','rc4' => 'arcfour','rc4-40' => 'arcfour'),'openssl' => array('rijndael-128' => 'aes-128','tripledes' => 'des-ede3','blowfish' => 'bf','cast-128' => 'cast5','arcfour' => 'rc4-40','rc4' => 'rc4-40'));// Notes://// - Rijndael-128 is, at the same time all three of AES-128,// AES-192 and AES-256. The only difference between them is// the key size. Rijndael-192, Rijndael-256 on the other hand// also have different block sizes and are NOT AES-compatible.//// - Blowfish is said to be supporting key sizes between// 4 and 56 bytes, but it appears that between MCrypt and// OpenSSL, only those of 16 and more bytes are compatible.// Also, don't know what MCrypt's 'blowfish-compat' is.//// - CAST-128/CAST5 produces a longer cipher when encrypted via// OpenSSL, but (strangely enough) can be decrypted by either// extension anyway.// Also, it appears that OpenSSL uses 16 rounds regardless of// the key size, while RFC2144 says that for key sizes lower// than 11 bytes, only 12 rounds should be used. This makes// it portable only with keys of between 11 and 16 bytes.//// - RC4 (ARCFour) has a strange implementation under OpenSSL.// Its 'rc4-40' cipher method seems to work flawlessly, yet// there's another one, 'rc4' that only works with a 16-byte key.//// - DES is compatible, but doesn't need an alias.//// Other seemingly matching ciphers between MCrypt, OpenSSL://// - RC2 is NOT compatible and only an obscure forum post// confirms that it is MCrypt's fault.}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 $length Output length (defaults to the selected digest size)* @param $info Optional context/application-specific info* @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;}self::strlen($salt) OR $salt = str_repeat("\0", $this->_digests[$digest]);$prk = hash_hmac($digest, $key, $salt, TRUE);$key = '';for ($key_block = '', $block_index = 1; self::strlen($key) < $length; $block_index++){$key_block = hash_hmac($digest, $key_block.$info.chr($block_index), $prk, TRUE);$key .= $key_block;}return self::substr($key, 0, $length);}// --------------------------------------------------------------------/*** __get() magic** @param string $key Property name* @return mixed*/public function __get($key){// Because aliasesif ($key === 'mode'){return array_search($this->_mode, $this->_modes[$this->_driver], TRUE);}elseif (in_array($key, array('cipher', 'driver', 'drivers', 'digests'), TRUE)){return $this->{'_'.$key};}return NULL;}// --------------------------------------------------------------------/*** Byte-safe strlen()** @param string $str* @return integer*/protected static function strlen($str){return (self::$func_override)? mb_strlen($str, '8bit'): strlen($str);}// --------------------------------------------------------------------/*** Byte-safe substr()** @param string $str* @param int $start* @param int $length* @return string*/protected static function substr($str, $start, $length = NULL){if (self::$func_override){// mb_substr($str, $start, null, '8bit') returns an empty// string on PHP 5.3isset($length) OR $length = ($start >= 0 ? self::strlen($str) - $start : -$start);return mb_substr($str, $start, $length, '8bit');}return isset($length)? substr($str, $start, $length): substr($str, $start);}}