You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
931 lines
23 KiB
931 lines
23 KiB
<?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->_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('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')) |
|
{ |
|
return random_bytes((int) $length); |
|
} |
|
|
|
return ($this->_driver === 'mcrypt') |
|
? mcrypt_create_iv($length, MCRYPT_DEV_URANDOM) |
|
: openssl_random_pseudo_bytes($length); |
|
} |
|
|
|
// -------------------------------------------------------------------- |
|
|
|
/** |
|
* 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) |
|
? mcrypt_create_iv($iv_size, MCRYPT_DEV_URANDOM) |
|
: 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'])) |
|
? openssl_random_pseudo_bytes($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 necessary |
|
if (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 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', |
|
'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 aliases |
|
if ($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.3 |
|
isset($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); |
|
} |
|
}
|
|
|