summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--system/libraries/Encryption.php887
-rw-r--r--system/libraries/Session/drivers/Session_cookie.php69
-rw-r--r--tests/codeigniter/libraries/Encryption_test.php389
-rw-r--r--tests/mocks/autoloader.php53
-rw-r--r--tests/mocks/libraries/encryption.php39
-rw-r--r--user_guide_src/source/changelog.rst17
-rw-r--r--user_guide_src/source/installation/upgrade_200.rst6
-rw-r--r--user_guide_src/source/installation/upgrade_300.rst20
-rw-r--r--user_guide_src/source/libraries/encrypt.rst203
-rw-r--r--user_guide_src/source/libraries/encryption.rst616
10 files changed, 2112 insertions, 187 deletions
diff --git a/system/libraries/Encryption.php b/system/libraries/Encryption.php
new file mode 100644
index 000000000..3a5409839
--- /dev/null
+++ b/system/libraries/Encryption.php
@@ -0,0 +1,887 @@
+<?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 = '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' => '',
+ 'gcm' => 'gcm',
+ 'xts' => 'xts'
+ )
+ );
+
+ /**
+ * 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' => 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'])
+ {
+ return show_error('Encryption: Unable to find an available encryption driver.');
+ }
+
+ $this->initialize($params);
+ if ( ! isset($this->_key) && strlen($key = config_item('encryption_key')) > 0)
+ {
+ $this->_key = $key;
+ }
+
+ 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['openssl'] === TRUE)
+ ? 'openssl'
+ : 'mcrypt';
+
+ 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']))
+ {
+ $params['mode'] = strtolower($params['mode']);
+ if ( ! isset($this->_modes['mcrypt'][$params['mode']]))
+ {
+ log_message('error', 'Encryption: MCrypt mode '.strtotupper($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('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']))
+ {
+ $params['mode'] = strtolower($params['mode']);
+ if ( ! isset($this->_modes['openssl'][$params['mode']]))
+ {
+ log_message('error', 'Encryption: OpenSSL mode '.strtotupper($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('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;
+ }
+
+ isset($params['key']) OR $params['key'] = $this->hkdf($this->_key, 'sha512', NULL, 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;
+ }
+ elseif ( ! isset($params['iv']))
+ {
+ // The greater-than-1 comparison is mostly a work-around for a bug,
+ // where 1 is returned for ARCFour instead of 0.
+ $params['iv'] = (($iv_size = mcrypt_enc_get_iv_size($params['handle'])) > 1)
+ ? mcrypt_create_iv($iv_size, MCRYPT_DEV_URANDOM)
+ : NULL;
+ }
+
+ // CAST-128 compatibility (http://tools.ietf.org/rfc/rfc2144.txt)
+ //
+ // RFC2144 says that keys shorter than 16 bytes are to be padded with
+ // zero bytes to 16 bytes, but (surprise) MCrypt doesn't do that.
+ if ($params['cipher'] === 'cast-128' && ($kl = strlen($params['key'])) < 16)
+ {
+ $params['key'] .= str_repeat("\x0", 16 - $kl);
+ }
+
+ 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.
+ 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 - (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')
+ ? $params['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;
+ }
+ elseif ( ! isset($params['iv']))
+ {
+ $params['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!
+ $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 (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 (strlen($data) <= $digest_size)
+ {
+ return FALSE;
+ }
+
+ $hmac_input = substr($data, 0, $digest_size);
+ $data = 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);
+ }
+
+ if (isset($params['iv']) && strncmp($params['iv'], $data, $iv_size = strlen($params['iv'])) === 0)
+ {
+ $data = substr($data, $iv_size);
+ }
+
+ isset($params['key']) OR $params['key'] = $this->hkdf($this->_key, 'sha512', NULL, 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;
+ }
+ elseif ( ! isset($params['iv']))
+ {
+ // 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')
+ {
+ $params['iv'] = substr($data, 0, $iv_size);
+ $data = substr($data, $iv_size);
+ }
+ else
+ {
+ // MCrypt is dumb and this is ignored, only size matters
+ $params['iv'] = str_repeat("\x0", $iv_size);
+ }
+ }
+ else
+ {
+ $params['iv'] = NULL;
+ }
+ }
+
+ // CAST-128 compatibility (http://tools.ietf.org/rfc/rfc2144.txt)
+ //
+ // RFC2144 says that keys shorter than 16 bytes are to be padded with
+ // zero bytes to 16 bytes, but (surprise) MCrypt doesn't do that.
+ if ($params['cipher'] === 'cast-128' && ($kl = strlen($params['key'])) < 16)
+ {
+ $params['key'] .= str_repeat("\x0", 16 - $kl);
+ }
+
+ 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);
+ // Remove PKCS#7 padding, if necessary
+ if (in_array(strtolower(mcrypt_enc_get_modes_name($params['handle'])), array('cbc', 'ecb'), TRUE))
+ {
+ $data = substr($data, 0, -ord($data[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 ( ! isset($params['iv']))
+ {
+ if ($iv_size = openssl_cipher_iv_length($params['handle']))
+ {
+ $params['iv'] = substr($data, 0, $iv_size);
+ $data = substr($data, $iv_size);
+ }
+ else
+ {
+ $params['iv'] = NULL;
+ }
+ }
+
+ return empty($params['handle'])
+ ? FALSE
+ : openssl_decrypt(
+ $data,
+ $params['handle'],
+ $params['key'],
+ 1, // DO NOT TOUCH!
+ $params['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' => ($this->_mode !== 'gcm' ? 'sha512' : NULL),
+ '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 ($params['mode'] === 'gcm' OR (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'],
+ 'iv' => isset($params['iv']) ? $params['iv'] : NULL,
+ '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, RFC2144 says that the cipher supports key sizes
+ // between 5 and 16 bytes by the implementation actually
+ // zero-padding them to 16 bytes, but MCrypt doesn't do that.
+ //
+ // - 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;
+ }
+
+ 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)
+ {
+ // 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;
+ }
+
+}
+
+/* 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 c8dfad6c9..79712ad94 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,38 +383,41 @@ 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);
-
- // Time-attack-safe comparison
- $hmac_check = hash_hmac('sha1', $session, $this->encryption_key);
- $diff = 0;
- for ($i = 0; $i < 40; $i++)
+ else
{
- $diff |= ord($hmac[$i]) ^ ord($hmac_check[$i]);
- }
+ if (($len = strlen($session) - 40) <= 0)
+ {
+ log_message('error', 'Session: The session cookie was not signed.');
+ return FALSE;
+ }
- if ($diff !== 0)
- {
- log_message('error', 'The session cookie data did not match what was expected.');
- $this->sess_destroy();
- return FALSE;
- }
+ // Check cookie authentication
+ $hmac = substr($session, $len);
+ $session = substr($session, 0, $len);
- // Check for encryption
- if ($this->sess_encrypt_cookie === TRUE)
- {
- // Decrypt the cookie data
- $session = $this->CI->encrypt->decode($session);
+ // Time-attack-safe comparison
+ $hmac_check = hash_hmac('sha1', $session, $this->encryption_key);
+ $diff = 0;
+ for ($i = 0; $i < 40; $i++)
+ {
+ $diff |= ord($hmac[$i]) ^ ord($hmac_check[$i]);
+ }
+
+ if ($diff !== 0)
+ {
+ 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
@@ -731,11 +734,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..54db2b42d
--- /dev/null
+++ b/tests/codeigniter/libraries/Encryption_test.php
@@ -0,0 +1,389 @@
+<?php
+
+class Encryption_test extends CI_TestCase {
+
+ public function set_up()
+ {
+ $this->encryption = new Mock_Libraries_Encryption();
+ }
+
+ /**
+ * __construct test
+ *
+ * Covers behavior with $config['encryption_key'] set or not
+ */
+ public function test___construct()
+ {
+ // Assume no configuration from set_up()
+ $this->assertNull($this->encryption->get_key());
+
+ // Try with an empty value
+ $this->ci_set_config('encryption_key');
+ $this->encrypt = new Mock_Libraries_Encryption();
+ $this->assertNull($this->encrypt->get_key());
+
+ $this->ci_set_config('encryption_key', str_repeat("\x0", 16));
+ $this->encrypt = new Mock_Libraries_Encryption();
+ $this->assertEquals(str_repeat("\x0", 16), $this->encrypt->get_key());
+ }
+
+ // --------------------------------------------------------------------
+
+ /**
+ * hkdf() test
+ *
+ * Applies test vectors described in Appendix A(1-3) RFC5869.
+ * Described vectors 4-7 SHA-1, which we don't support and are
+ * therefore excluded.
+ *
+ * Because our implementation is a single method instead of being
+ * split into hkdf_extract() and hkdf_expand(), we cannot test for
+ * the PRK value. As long as the OKM is correct though, it's fine.
+ *
+ * @link https://tools.ietf.org/rfc/rfc5869.txt
+ */
+ public function test_hkdf()
+ {
+ $vectors = array(
+ // 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"
+ ),
+ // 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",
+ ),
+ // 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));
+
+ // CI-specific test for an invalid digest
+ $this->assertFalse($this->encryption->hkdf('fobar', 'sha1'));
+ }
+
+ // --------------------------------------------------------------------
+
+ /**
+ * _get_params() test
+ *
+ * @uses Mock_Libraries_Encryption::__get_params()
+ */
+ public function test__get_params()
+ {
+ $key = str_repeat("\x0", 16);
+
+ // Invalid custom parameters
+ $params = array(
+ // No cipher, mode or key
+ array('cipher' => 'aes-128', 'mode' => 'cbc'),
+ array('cipher' => 'aes-128', 'key' => $key),
+ array('mode' => 'cbc', 'key' => $key),
+ // No HMAC key or not a valid digest
+ array('cipher' => 'aes-128', 'mode' => 'cbc', 'key' => $key),
+ array('cipher' => 'aes-128', 'mode' => 'cbc', 'key' => $key, 'hmac_digest' => 'sha1', 'hmac_key' => $key),
+ // Invalid mode
+ array('cipher' => 'aes-128', 'mode' => 'foo', 'key' => $key, 'hmac_digest' => 'sha256', 'hmac_key' => $key)
+ );
+
+ for ($i = 0, $c = count($params); $i < $c; $i++)
+ {
+ $this->assertFalse($this->encryption->__get_params($params[$i]));
+ }
+
+ // Valid parameters
+ $params = array(
+ 'cipher' => 'aes-128',
+ 'mode' => 'cbc',
+ 'key' => str_repeat("\x0", 16),
+ 'hmac_key' => str_repeat("\x0", 16)
+ );
+
+ $this->assertTrue(is_array($this->encryption->__get_params($params)));
+
+ $params['iv'] = NULL;
+ $params['base64'] = TRUE;
+ $params['hmac_digest'] = 'sha512';
+
+ // Including all parameters
+ $params = array(
+ 'cipher' => 'aes-128',
+ 'mode' => 'cbc',
+ 'key' => str_repeat("\x0", 16),
+ 'iv' => str_repeat("\x0", 16),
+ 'raw_data' => TRUE,
+ 'hmac_key' => str_repeat("\x0", 16),
+ 'hmac_digest' => 'sha256'
+ );
+
+ $output = $this->encryption->__get_params($params);
+ unset($output['handle'], $params['raw_data']);
+ $params['base64'] = FALSE;
+ $this->assertEquals($params, $output);
+
+ // HMAC disabled
+ unset($params['hmac_key'], $params['hmac_digest']);
+ $params['hmac'] = $params['raw_data'] = FALSE;
+ $output = $this->encryption->__get_params($params);
+ unset($output['handle'], $params['hmac'], $params['raw_data']);
+ $params['base64'] = TRUE;
+ $params['hmac_digest'] = $params['hmac_key'] = NULL;
+ $this->assertEquals($params, $output);
+ }
+
+ // --------------------------------------------------------------------
+
+ /**
+ * initialize(), encrypt(), decrypt() test
+ *
+ * Testing the three methods separately is not realistic as they are
+ * designed to work together. A more thorough test for initialize()
+ * though is the OpenSSL/MCrypt compatibility test.
+ */
+ public function test_initialize_encrypt_decrypt()
+ {
+ $message = 'This is a plain-text message.';
+ $key = "\xd0\xc9\x08\xc4\xde\x52\x12\x6e\xf8\xcc\xdb\x03\xea\xa0\x3a\x5c";
+
+ // Default state (AES-128/Rijndael-128 in CBC mode)
+ $this->encryption->initialize(array('key' => $key));
+
+ // Was the key properly set?
+ $this->assertEquals($key, $this->encryption->get_key());
+
+ $this->assertEquals($message, $this->encryption->decrypt($this->encryption->encrypt($message)));
+
+ // Try DES in ECB mode, just for the sake of changing stuff
+ $this->encryption->initialize(array('cipher' => 'des', 'mode' => 'ecb'));
+ $this->assertEquals($message, $this->encryption->decrypt($this->encryption->encrypt($message)));
+ }
+
+ // --------------------------------------------------------------------
+
+ /**
+ * encrypt(), decrypt test with custom parameters
+ */
+ public function test_encrypt_decrypt_custom()
+ {
+ $message = 'Another plain-text message.';
+
+ // A random invalid parameter
+ $this->assertFalse($this->encryption->encrypt($message, array('foo')));
+ $this->assertFalse($this->encryption->decrypt($message, array('foo')));
+
+ // Custom IV (we'll check it), no HMAC, binary output
+ $params = array(
+ 'cipher' => 'tripledes',
+ 'mode' => 'cfb',
+ 'key' => str_repeat("\x1", 16),
+ 'iv' => str_repeat("\x2", 8),
+ 'base64' => FALSE,
+ 'hmac' => FALSE
+ );
+
+ $ciphertext = $this->encryption->encrypt($message, $params);
+ $this->assertEquals(0, strncmp($params['iv'], $ciphertext, 8));
+
+ // IV should be found in the cipher-text, no matter if it was supplied or not
+ $this->assertEquals($message, $this->encryption->decrypt($ciphertext, $params));
+ unset($params['iv']);
+ $this->assertEquals($message, $this->encryption->decrypt($ciphertext, $params));
+ }
+
+ // --------------------------------------------------------------------
+
+ /**
+ * _mcrypt_get_handle() test
+ */
+ public function test__mcrypt_get_handle()
+ {
+ if ($this->encryption->drivers['mcrypt'] === FALSE)
+ {
+ return $this->markTestAsSkipped('Cannot test MCrypt because it is not available.');
+ }
+
+ $this->assertTrue(is_resource($this->encryption->__driver_get_handle('mcrypt', 'rijndael-128', 'cbc')));
+ }
+
+ // --------------------------------------------------------------------
+
+ /**
+ * _openssl_get_handle() test
+ */
+ public function test__openssl_mcrypt_get_handle()
+ {
+ if ($this->encryption->drivers['openssl'] === FALSE)
+ {
+ return $this->markTestAsSkipped('Cannot test OpenSSL because it is not available.');
+ }
+
+ $this->assertEquals('aes-128-cbc', $this->encryption->__driver_get_handle('openssl', 'aes-128', 'cbc'));
+ $this->assertEquals('rc4-40', $this->encryption->__driver_get_handle('openssl', 'rc4-40', 'stream'));
+ }
+
+ // --------------------------------------------------------------------
+
+ /**
+ * OpenSSL/MCrypt portability test
+ *
+ * Amongst the obvious stuff, _cipher_alias() is also tested here.
+ */
+ 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.';
+
+ // Format is: <Cipher name>, <Cipher mode>, <Key size>
+ $portable = array(
+ array('aes-128', 'cbc', 16),
+ array('aes-128', 'cfb', 16),
+ array('aes-128', 'cfb8', 16),
+ array('aes-128', 'ofb', 16),
+ array('aes-128', 'ecb', 16),
+ array('aes-128', 'ctr', 16),
+ array('aes-192', 'cbc', 24),
+ array('aes-192', 'cfb', 24),
+ array('aes-192', 'cfb8', 24),
+ array('aes-192', 'ofb', 24),
+ array('aes-192', 'ecb', 24),
+ array('aes-192', 'ctr', 24),
+ array('aes-256', 'cbc', 32),
+ array('aes-256', 'cfb', 32),
+ array('aes-256', 'cfb8', 32),
+ array('aes-256', 'ofb', 32),
+ array('aes-256', 'ecb', 32),
+ array('aes-256', 'ctr', 32),
+ array('des', 'cbc', 7),
+ array('des', 'cfb', 7),
+ array('des', 'cfb8', 7),
+ array('des', 'ofb', 7),
+ array('des', 'ecb', 7),
+ array('tripledes', 'cbc', 7),
+ array('tripledes', 'cfb', 7),
+ array('tripledes', 'cfb8', 7),
+ array('tripledes', 'ofb', 7),
+ array('tripledes', 'cbc', 14),
+ array('tripledes', 'cfb', 14),
+ array('tripledes', 'cfb8', 14),
+ array('tripledes', 'ofb', 14),
+ array('tripledes', 'cbc', 21),
+ array('tripledes', 'cfb', 21),
+ array('tripledes', 'cfb8', 21),
+ array('tripledes', 'ofb', 21),
+ array('blowfish', 'cbc', 16),
+ array('blowfish', 'cfb', 16),
+ array('blowfish', 'ofb', 16),
+ array('blowfish', 'ecb', 16),
+ array('blowfish', 'cbc', 56),
+ array('blowfish', 'cfb', 56),
+ array('blowfish', 'ofb', 56),
+ array('blowfish', 'ecb', 56),
+ array('cast5', 'cbc', 5),
+ array('cast5', 'cfb', 5),
+ array('cast5', 'ofb', 5),
+ array('cast5', 'ecb', 5),
+ array('cast5', 'cbc', 8),
+ array('cast5', 'cfb', 8),
+ array('cast5', 'ofb', 8),
+ array('cast5', 'ecb', 8),
+ array('cast5', 'cbc', 10),
+ array('cast5', 'cfb', 10),
+ array('cast5', 'ofb', 10),
+ array('cast5', 'ecb', 10),
+ array('cast5', 'cbc', 16),
+ array('cast5', 'cfb', 16),
+ array('cast5', 'ofb', 16),
+ array('cast5', 'ecb', 16),
+ array('rc4', 'stream', 5),
+ array('rc4', 'stream', 8),
+ array('rc4', 'stream', 16),
+ array('rc4', 'stream', 32),
+ array('rc4', 'stream', 64),
+ array('rc4', 'stream', 128),
+ array('rc4', 'stream', 256)
+ );
+ $driver_index = array('mcrypt', 'openssl');
+
+ foreach ($portable as &$test)
+ {
+ // Add some randomness to the selected driver
+ $driver = mt_rand(0,1);
+ $params = array(
+ 'driver' => $driver_index[$driver],
+ 'cipher' => $test[0],
+ 'mode' => $test[1],
+ 'key' => openssl_random_pseudo_bytes($test[2])
+ );
+
+ $this->encryption->initialize($params);
+ $ciphertext = $this->encryption->encrypt($message);
+
+ $driver = (int) ! $driver;
+ $params['driver'] = $driver_index[$driver];
+
+ $this->encryption->initialize($params);
+ $this->assertEquals($message, $this->encryption->decrypt($ciphertext));
+ }
+ }
+
+ // --------------------------------------------------------------------
+
+ /**
+ * __get() test
+ */
+ public function test_magic_get()
+ {
+ $this->assertNull($this->encryption->foo);
+ $this->assertEquals(array('mcrypt', 'openssl'), array_keys($this->encryption->drivers));
+
+ // 'stream' mode is translated into an empty string for OpenSSL
+ $this->encryption->initialize(array('cipher' => 'rc4', 'mode' => 'stream'));
+ $this->assertEquals('stream', $this->encryption->mode);
+ }
+
+} \ 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/tests/mocks/libraries/encryption.php b/tests/mocks/libraries/encryption.php
new file mode 100644
index 000000000..028eecc72
--- /dev/null
+++ b/tests/mocks/libraries/encryption.php
@@ -0,0 +1,39 @@
+<?php
+
+class Mock_Libraries_Encryption extends CI_Encryption {
+
+ /**
+ * __get_params()
+ *
+ * Allows public calls to the otherwise protected _get_params().
+ */
+ public function __get_params($params)
+ {
+ return $this->_get_params($params);
+ }
+
+ // --------------------------------------------------------------------
+
+ /**
+ * get_key()
+ *
+ * Allows checking for key changes.
+ */
+ public function get_key()
+ {
+ return $this->_key;
+ }
+
+ // --------------------------------------------------------------------
+
+ /**
+ * __driver_get_handle()
+ *
+ * Allows checking for _mcrypt_get_handle(), _openssl_get_handle()
+ */
+ public function __driver_get_handle($driver, $cipher, $mode)
+ {
+ return $this->{'_'.$driver.'_get_handle'}($cipher, $mode);
+ }
+
+} \ No newline at end of file
diff --git a/user_guide_src/source/changelog.rst b/user_guide_src/source/changelog.rst
index 4a54593f0..1e43ab23c 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.
@@ -360,11 +368,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 ``&nbsp;`` 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.
@@ -579,7 +582,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>` method ``join()`` only escaped one set of conditions.
- Fixed a bug (#1321) - ``CI_Exceptions`` couldn't find the *errors/* directory 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>` ``encode_from_legacy()`` didn't set back the encrypt mode on failure.
- Fixed a bug (#145) - :doc:`Database Class <database/index>` method ``compile_binds()`` failed when the bind marker was present in a literal string within the query.
- Fixed a bug in :doc:`Query Builder <database/query_builder>` method ``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.
@@ -1295,7 +1298,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:`Encrypt 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 292c1de9d..59df7c303 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,24 @@ 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>`_ (and /dev/urandom
+availability) 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/encrypt.rst b/user_guide_src/source/libraries/encrypt.rst
new file mode 100644
index 000000000..faff39975
--- /dev/null
+++ b/user_guide_src/source/libraries/encrypt.rst
@@ -0,0 +1,203 @@
+#############
+Encrypt Class
+#############
+
+The Encrypt Class provides two-way data encryption. It uses a scheme
+that either compiles the message using a randomly hashed bitwise XOR
+encoding scheme, or is encrypted using the Mcrypt library. If Mcrypt is
+not available on your server the encoded message will still provide a
+reasonable degree of security for encrypted sessions or other such
+"light" purposes. If Mcrypt is available, you'll be provided with a high
+degree of security appropriate for storage.
+
+.. important:: This library has been DEPRECATED and is only kept for
+ backwards compatibility. Please use the new :doc:`Encryption Library
+ <encryption>`.
+
+.. contents::
+ :local:
+
+.. raw:: html
+
+ <div class="custom-index container"></div>
+
+*************************
+Using the Encrypt Library
+*************************
+
+Setting your Key
+================
+
+A *key* is a piece of information that controls the cryptographic
+process and permits an encrypted string to be decoded. In fact, the key
+you chose will provide the **only** means to decode data that was
+encrypted with that key, so not only must you choose the key carefully,
+you must never change it if you intend use it for persistent data.
+
+It goes without saying that you should guard your key carefully. Should
+someone gain access to your key, the data will be easily decoded. If
+your server is not totally under your control it's impossible to ensure
+key security so you may want to think carefully before using it for
+anything that requires high security, like storing credit card numbers.
+
+To take maximum advantage of the encryption algorithm, your key should
+be 32 characters in length (256 bits). The key should be as random a
+string as you can concoct, with numbers and uppercase and lowercase
+letters. Your key should **not** be a simple text string. In order to be
+cryptographically secure it needs to be as random as possible.
+
+Your key can be either stored in your **application/config/config.php**, or
+you can design your own storage mechanism and pass the key dynamically
+when encoding/decoding.
+
+To save your key to your **application/config/config.php**, open the file
+and set::
+
+ $config['encryption_key'] = "YOUR KEY";
+
+Message Length
+==============
+
+It's important for you to know that the encoded messages the encryption
+function generates will be approximately 2.6 times longer than the
+original message. For example, if you encrypt the string "my super
+secret data", which is 21 characters in length, you'll end up with an
+encoded string that is roughly 55 characters (we say "roughly" because
+the encoded string length increments in 64 bit clusters, so it's not
+exactly linear). Keep this information in mind when selecting your data
+storage mechanism. Cookies, for example, can only hold 4K of
+information.
+
+Initializing the Class
+======================
+
+Like most other classes in CodeIgniter, the Encrypt class is
+initialized in your controller using the ``$this->load->library()``
+method::
+
+ $this->load->library('encrypt');
+
+Once loaded, the Encrypt library object will be available using::
+
+ $this->encrypt
+
+***************
+Class Reference
+***************
+
+.. class:: CI_Encrypt
+
+ .. method:: encode($string[, $key = ''])
+
+ :param string $string: Data to encrypt
+ :param string $key: Encryption key
+ :returns: Encrypted string
+ :rtype: string
+
+ Performs the data encryption and returns it as a string. Example::
+
+ $msg = 'My secret message';
+
+ $encrypted_string = $this->encrypt->encode($msg);
+
+ You can optionally pass your encryption key via the second parameter if
+ you don't want to use the one in your config file::
+
+ $msg = 'My secret message';
+ $key = 'super-secret-key';
+
+ $encrypted_string = $this->encrypt->encode($msg, $key);
+
+ .. method:: decode($string[, $key = ''])
+
+ :param string $string: String to decrypt
+ :param string $key: Encryption key
+ :returns: Plain-text string
+ :rtype: string
+
+ Decrypts an encoded string. Example::
+
+ $encrypted_string = 'APANtByIGI1BpVXZTJgcsAG8GZl8pdwwa84';
+
+ $plaintext_string = $this->encrypt->decode($encrypted_string);
+
+ You can optionally pass your encryption key via the second parameter if
+ you don't want to use the one in your config file::
+
+ $msg = 'My secret message';
+ $key = 'super-secret-key';
+
+ $encrypted_string = $this->encrypt->decode($msg, $key);
+
+ .. method:: set_cipher($cipher)
+
+ :param int $cipher: Valid PHP MCrypt cypher constant
+ :returns: CI_Encrypt instance (method chaining)
+ :rtype: CI_Encrypt
+
+ Permits you to set an Mcrypt cipher. By default it uses
+ ``MCRYPT_RIJNDAEL_256``. Example::
+
+ $this->encrypt->set_cipher(MCRYPT_BLOWFISH);
+
+ Please visit php.net for a list of `available ciphers <http://php.net/mcrypt>`_.
+
+ If you'd like to manually test whether your server supports MCrypt you
+ can use::
+
+ echo extension_loaded('mcrypt') ? 'Yup' : 'Nope';
+
+ .. method:: set_mode($mode)
+
+ :param int $mode: Valid PHP MCrypt mode constant
+ :returns: CI_Encrypt instance (method chaining)
+ :rtype: CI_Encrypt
+
+ Permits you to set an Mcrypt mode. By default it uses **MCRYPT_MODE_CBC**.
+ Example::
+
+ $this->encrypt->set_mode(MCRYPT_MODE_CFB);
+
+ Please visit php.net for a list of `available modes <http://php.net/mcrypt>`_.
+
+ .. method:: encode_from_legacy($string[, $legacy_mode = MCRYPT_MODE_ECB[, $key = '']])
+
+ :param string $string: String to encrypt
+ :param int $legacy_mode: Valid PHP MCrypt cipher constant
+ :param string $key: Encryption key
+ :returns: Newly encrypted string
+ :rtype: string
+
+ Enables you to re-encode data that was originally encrypted with
+ CodeIgniter 1.x to be compatible with the Encrypt library in
+ CodeIgniter 2.x. It is only necessary to use this method if you have
+ encrypted data stored permanently such as in a file or database and are
+ on a server that supports Mcrypt. "Light" use encryption such as
+ encrypted session data or transitory encrypted flashdata require no
+ intervention on your part. However, existing encrypted Sessions will be
+ destroyed since data encrypted prior to 2.x will not be decoded.
+
+ .. important::
+ **Why only a method to re-encode the data instead of maintaining legacy
+ methods for both encoding and decoding?** The algorithms in the
+ Encrypt library have improved in CodeIgniter 2.x both for performance
+ and security, and we do not wish to encourage continued use of the older
+ methods. You can of course extend the Encryption library if you wish and
+ replace the new methods with the old and retain seamless compatibility
+ with CodeIgniter 1.x encrypted data, but this a decision that a
+ developer should make cautiously and deliberately, if at all.
+
+ ::
+
+ $new_data = $this->encrypt->encode_from_legacy($old_encrypted_string);
+
+ ====================== =============== =======================================================================
+ Parameter Default Description
+ ====================== =============== =======================================================================
+ **$orig_data** n/a The original encrypted data from CodeIgniter 1.x's Encryption library
+ **$legacy_mode** MCRYPT_MODE_ECB The Mcrypt mode that was used to generate the original encrypted data.
+ CodeIgniter 1.x's default was MCRYPT_MODE_ECB, and it will assume that
+ to be the case unless overridden by this parameter.
+ **$key** n/a The encryption key. This it typically specified in your config file as
+ outlined above.
+ ====================== =============== ======================================================================= \ No newline at end of file
diff --git a/user_guide_src/source/libraries/encryption.rst b/user_guide_src/source/libraries/encryption.rst
index 567a1e990..cedc8d381 100644
--- a/user_guide_src/source/libraries/encryption.rst
+++ b/user_guide_src/source/libraries/encryption.rst
@@ -1,14 +1,19 @@
-################
-Encryption Class
-################
-
-The Encryption Class provides two-way data encryption. It uses a scheme
-that either compiles the message using a randomly hashed bitwise XOR
-encoding scheme, or is encrypted using the Mcrypt library. If Mcrypt is
-not available on your server the encoded message will still provide a
-reasonable degree of security for encrypted sessions or other such
-"light" purposes. If Mcrypt is available, you'll be provided with a high
-degree of security appropriate for storage.
+##################
+Encryption Library
+##################
+
+The Encryption Library provides two-way data encryption. To do so in
+a cryptographically secure way, it utilizes PHP extensions that are
+unfortunately not always available on all systems.
+You must meet one of the following dependancies in order to use this
+library:
+
+- `OpenSSL <http://php.net/openssl>`_ (and PHP 5.3.3)
+- `MCrypt <http://php.net/mcrypt>`_ (and `MCRYPT_DEV_URANDOM` availability)
+
+If neither of the above dependancies is met, we simply cannot offer
+you a good enough implementation to meet the high standards required
+for proper cryptography.
.. contents::
:local:
@@ -21,177 +26,530 @@ degree of security appropriate for storage.
Using the Encryption Library
****************************
-Setting your Key
+Initializing the Class
+======================
+
+Like most other classes in CodeIgniter, the Encryption library is
+initialized in your controller using the ``$this->load->library()``
+method::
+
+ $this->load->library('encrypt');
+
+Once loaded, the Encryption library object will be available using::
+
+ $this->encrypt
+
+Default behavior
================
-A *key* is a piece of information that controls the cryptographic
-process and permits an encrypted string to be decoded. In fact, the key
-you chose will provide the **only** means to decode data that was
-encrypted with that key, so not only must you choose the key carefully,
-you must never change it if you intend use it for persistent data.
+By default, the Encryption Library will use the AES-128 cipher in CBC
+mode, using your configured *encryption_key* and SHA512 HMAC authentication.
+
+.. note:: AES-128 is chosen both because it is proven to be strong and
+ because of its wide availability across different cryptographic
+ software and programming languages' APIs.
+
+However, the *encryption_key* is not used as is.
+
+If you are somewhat familiar with cryptography, you should already know
+that a HMAC also requires a secret key and using the same key for both
+encryption and authentication is a bad practice.
+
+Because of that, two separate keys are derived from your already configured
+*encryption_key*: one for encryption and one for authentication. This is
+done via a technique called `HMAC-based Key Derivation Function
+<http://en.wikipedia.org/wiki/HKDF>`_ (HKDF).
+
+Setting your encryption_key
+===========================
+
+An *encryption key* is a piece of information that controls the
+cryptographic process and permits a plain-text string to be encrypted,
+and afterwards - decrypted. It is the secret "ingredient" in the whole
+process that allows you to be the only one who is able to decrypt data
+that you've decided to hide from the eyes of the public.
+After one key is used to encrypt data, that same key provides the **only**
+means to decrypt it, so not only must you chose one carefully, but you
+must not lose it or you will also use the encrypted data.
+
+It must be noted that to ensure maximum security, such key *should* not
+only be as strong as possible, but also often changed. Such behavior
+however is rarely practical or possible to implement, and that is why
+CodeIgniter gives you the ability to configure a single key that is to be
+used (almost) every time.
It goes without saying that you should guard your key carefully. Should
-someone gain access to your key, the data will be easily decoded. If
+someone gain access to your key, the data will be easily decrypted. If
your server is not totally under your control it's impossible to ensure
key security so you may want to think carefully before using it for
anything that requires high security, like storing credit card numbers.
-To take maximum advantage of the encryption algorithm, your key should
-be 32 characters in length (256 bits). The key should be as random a
-string as you can concoct, with numbers and uppercase and lowercase
-letters. Your key should **not** be a simple text string. In order to be
-cryptographically secure it needs to be as random as possible.
+Your encryption key should be as long as the encyption algorithm in use
+allows. For AES-128, that's 128 bits or 16 bytes (charcters) long. The
+key should be as random as possible and it should **not** be a simple
+text string.
+
+You will find a table below that shows the supported key lengths of
+different ciphers.
-Your key can be either stored in your **application/config/config.php**, or
+The key can be either stored in your *application/config/config.php*, or
you can design your own storage mechanism and pass the key dynamically
-when encoding/decoding.
+when encrypting/decrypting.
-To save your key to your **application/config/config.php**, open the file
+To save your key to your *application/config/config.php*, open the file
and set::
- $config['encryption_key'] = "YOUR KEY";
+ $config['encryption_key'] = 'YOUR KEY';
+
+.. _ciphers-and-modes:
+
+Supported encryption ciphers and modes
+======================================
+
+.. note:: The terms 'cipher' and 'encryption algorithm' are interchangeable.
+
+Portable ciphers
+----------------
+
+Because MCrypt and OpenSSL (also called drivers throughout this document)
+each support different sets of encryption algorithms and often implement
+them in different ways, our Encryption library is designed to use them in
+a portable fashion, or in other words - it enables you to use them
+interchangeably, at least for the ciphers supported by both drivers.
+
+It is also implemented in a way that aims to match the standard
+implementations in other programming languages and libraries.
+
+Here's a list of the so called "portable" ciphers, where
+"CodeIgniter name" is the string value that you'd have to pass to the
+Encryption library to use that cipher:
+
+======================== ================== ============================ ===============================
+Cipher name CodeIgniter name Key lengths (bits / bytes) Supported modes
+======================== ================== ============================ ===============================
+AES-128 / Rijndael-128 aes-128 128 / 16 CBC, CTR, CFB, CFB8, OFB, ECB
+AES-192 aes-192 192 / 24 CBC, CTR, CFB, CFB8, OFB, ECB
+AES-256 aes-256 256 / 32 CBC, CTR, CFB, CFB8, OFB, ECB
+DES des 56 / 7 CBC, CFB, CFB8, OFB, ECB
+TripleDES tripledes 56 / 7, 112 / 14, 168 / 21 CBC, CFB, CFB8, OFB
+Blowfish blowfish 128-448 / 16-56 CBC, CFB, OFB, ECB
+CAST5 / CAST-128 cast5 40-128 / 5-16 CBC, CFB, OFB, ECB
+RC4 / ARCFour rc4 40-2048 / 5-256 Stream
+======================== ================== ============================ ===============================
+
+.. important:: Because of how MCrypt works, if you fail to provide a key
+ with the appropriate length, you might end up using a different
+ algorithm than the one configured, so be really careful with that!
+
+.. note:: In case it isn't clear from the above table, Blowfish, CAST5
+ and RC4 support variable length keys. That is, any number in the
+ shown ranges is valid, although in bit terms that only happens
+ in 8-bit increments.
+
+.. note:: Even though CAST5 supports key lengths lower than 128 bits
+ (16 bytes), in fact they will just be zero-padded to the
+ maximum length, as specified in `RFC 2144
+ <http://tools.ietf.org/rfc/rfc2144.txt>`_.
+
+.. note:: Blowfish supports key lengths as small as 32 bits (4 bytes), but
+ our tests have shown that only lengths of 128 bits (16 bytes) or
+ higher are properly supported by both MCrypt and OpenSSL. It is
+ also a bad practice to use such low-length keys anyway.
+
+Driver-specific ciphers
+-----------------------
+
+As noted above, MCrypt and OpenSSL support different sets of encryption
+ciphers. For portability reasons and because we haven't tested them
+properly, we do not advise you to use the ones that are driver-specific,
+but regardless, here's a list of most of them:
+
+
+============== ========= ============================== =========================================
+Cipher name Driver Key lengths (bits / bytes) Supported modes
+============== ========= ============================== =========================================
+AES-128 OpenSSL 128 / 16 CBC, CTR, CFB, CFB8, OFB, ECB, GCM, XTS
+AES-192 OpenSSL 192 / 24 CBC, CTR, CFB, CFB8, OFB, ECB, GCM, XTS
+AES-256 OpenSSL 256 / 32 CBC, CTR, CFB, CFB8, OFB, ECB, GCM, XTS
+Rijndael-128 MCrypt 128 / 16, 192 / 24, 256 / 32 CBC, CTR, CFB, CFB8, OFB, OFB8, ECB
+Rijndael-192 MCrypt 128 / 16, 192 / 24, 256 / 32 CBC, CTR, CFB, CFB8, OFB, OFB8, ECB
+Rijndael-256 MCrypt 128 / 16, 192 / 24, 256 / 32 CBC, CTR, CFB, CFB8, OFB, OFB8, ECB
+GOST MCrypt 256 / 32 CBC, CTR, CFB, CFB8, OFB, OFB8, ECB
+Twofish MCrypt 128 / 16, 192 / 24, 256 / 32 CBC, CTR, CFB, CFB8, OFB, OFB8, ECB
+CAST-256 MCrypt 128 / 16, 192 / 24, 256 / 32 CBC, CTR, CFB, CFB8, OFB, OFB8, ECB
+Loki97 MCrypt 128 / 16, 192 / 24, 256 / 32 CBC, CTR, CFB, CFB8, OFB, OFB8, ECB
+SaferPlus MCrypt 128 / 16, 192 / 24, 256 / 32 CBC, CTR, CFB, CFB8, OFB, OFB8, ECB
+Serpent MCrypt 128 / 16, 192 / 24, 256 / 32 CBC, CTR, CFB, CFB8, OFB, OFB8, ECB
+XTEA MCrypt 128 / 16 CBC, CTR, CFB, CFB8, OFB, OFB8, ECB
+RC2 MCrypt 8-1024 / 1-128 CBC, CTR, CFB, CFB8, OFB, OFB8, ECB
+RC2 OpenSSL 8-1024 / 1-128 CBC, CFB, OFB, ECB
+Camellia-128 OpenSSL 128 / 16 CBC, CFB, CFB8, OFB, ECB
+Camellia-192 OpenSSL 192 / 24 CBC, CFB, CFB8, OFB, ECB
+Camellia-256 OpenSSL 256 / 32 CBC, CFB, CFB8, OFB, ECB
+Seed OpenSSL 128 / 16 CBC, CFB, OFB, ECB
+============== ========= ============================== =========================================
+
+.. note:: If you wish to use one of those ciphers, you'd have to pass
+ its name in lower-case to the Encryption library.
+
+.. note:: You've probably noticed that all AES cipers (and Rijndael-128)
+ are also listed in the portable ciphers list. This is because
+ drivers support different modes for these ciphers. Also, it is
+ important to note that AES-128 and Rijndael-128 are actually
+ the same cipher, but **only** when used with a 128-bit key.
+
+.. note:: RC2 is listed as supported by both MCrypt and OpenSSL.
+ However, both drivers implement them differently and they
+ are not portable. It is probably worth noting that we only
+ found one obscure source confirming that it is MCrypt that
+ is not properly implementing it.
+
+.. _encryption-modes:
+
+Encryption modes
+----------------
+
+Different modes of encryption have different characteristics and serve
+for different purposes. Some are stronger than others, some are faster
+and some offer extra features.
+We are not going in depth into that here, we'll leave that to the
+cryptography experts. The table below is to provide brief informational
+reference to our more experienced users. If you are a beginner, just
+stick to the CBC mode - it is widely accepted as strong and secure for
+general purposes.
+
+=========== ================== ================= ===================================================================================================================================================
+Mode name CodeIgniter name Driver support Additional info
+=========== ================== ================= ===================================================================================================================================================
+CBC cbc MCrypt, OpenSSL A safe default choice
+CTR ctr MCrypt, OpenSSL Considered as theoretically better than CBC, but not as widely available
+CFB cfb MCrypt, OpenSSL N/A
+CFB8 cfb8 MCrypt, OpenSSL Same as CFB, but operates in 8-bit mode (not recommended).
+OFB ofb MCrypt, OpenSSL N/A
+OFB8 ofb8 MCrypt Same as OFB, but operates in 8-bit mode (not recommended).
+ECB ecb MCrypt, OpenSSL Ignores IV (not recommended).
+GCM gcm OpenSSL Provides authentication and therefore doesn't need a HMAC.
+XTS xts OpenSSL Usually used for encrypting random access data such as RAM or hard-disk storage.
+Stream stream MCrypt, OpenSSL This is not actually a mode, it just says that a stream cipher is being used. Required because of the general cipher+mode initialization process.
+=========== ================== ================= ===================================================================================================================================================
Message Length
==============
-It's important for you to know that the encoded messages the encryption
-function generates will be approximately 2.6 times longer than the
-original message. For example, if you encrypt the string "my super
-secret data", which is 21 characters in length, you'll end up with an
-encoded string that is roughly 55 characters (we say "roughly" because
-the encoded string length increments in 64 bit clusters, so it's not
-exactly linear). Keep this information in mind when selecting your data
-storage mechanism. Cookies, for example, can only hold 4K of
-information.
+It's probably important for you to know that an encrypted string is usually
+longer than the original, plain-text string (depending on the cipher).
-Initializing the Class
-======================
+This is influenced by the cipher algorithm itself, the IV prepended to the
+cipher-text and (unless you are using GCM mode) the HMAC authentication
+message that is also prepended. Furthermore, the encrypted message is also
+Base64-encoded so that it is safe for storage and transmission, regardless
+of a possible character set in use.
-Like most other classes in CodeIgniter, the Encryption class is
-initialized in your controller using the **$this->load->library** function::
+Keep this information in mind when selecting your data storage mechanism.
+Cookies, for example, can only hold 4K of information.
- $this->load->library('encrypt');
+.. _configuration:
-Once loaded, the Encrypt library object will be available using
-``$this->encrypt``
+Configuring the library
+=======================
-***************
-Class Reference
-***************
+For usability, performance, but also historical reasons tied to our old
+:doc:`Encrypt Class <encrypt>`, the Encryption library is designed to
+use repeatedly the same driver, encryption cipher, mode and key.
-.. class:: CI_Encrypt
+As noted in the "Default behavior" section above, this means using an
+auto-detected driver (OpenSSL has a higher priority), the AES-128 ciper
+in CBC mode, and your ``$config['encryption_key']`` value.
- .. method:: encode($string[, $key = ''])
+If you wish to change that however, you need to use the ``initialize()``
+method. It accepts an associative array of parameters, all of which are
+optional:
- :param string $string: Data to encrypt
- :param string $key: Encryption key
- :returns: Encrypted string
- :rtype: string
+======== ===============================================
+Option Possible values
+======== ===============================================
+driver 'mcrypt', 'openssl'
+cipher Cipher name (see :ref:`ciphers-and-modes`)
+mode Encryption mode (see :ref:`encryption-modes`)
+key Encryption key
+======== ===============================================
- Performs the data encryption and returns it as a string. Example::
+For example, if you were to change the encryption algorithm and
+mode to AES-256 in CTR mode, this is what you should do::
- $msg = 'My secret message';
+ $this->encryption->initialize(
+ array(
+ 'cipher' => 'aes-256',
+ 'mode' => 'ctr',
+ 'key' => '<a 32-character random string>'
+ )
+ );
- $encrypted_string = $this->encrypt->encode($msg);
+Note that we only mentioned that you want to change the ciper and mode,
+but we also included a key in the example. As previously noted, it is
+important that you choose a key with a proper size for the used algorithm.
- You can optionally pass your encryption key via the second parameter if
- you don't want to use the one in your config file::
+There's also the ability to change the driver, if for some reason you
+have both, but want to use MCrypt instead of OpenSSL::
- $msg = 'My secret message';
- $key = 'super-secret-key';
+ // Switch to the MCrypt driver
+ $this->encryption->initialize(array('driver' => 'mcrypt'));
- $encrypted_string = $this->encrypt->encode($msg, $key);
+ // Switch back to the OpenSSL driver
+ $this->encryption->initialize(array('driver' => 'openssl'));
- .. method:: decode($string[, $key = ''])
+Encrypting and decrypting data
+==============================
- :param string $string: String to decrypt
- :param string $key: Encryption key
- :returns: Plain-text string
- :rtype: string
+Encrypting and decrypting data with the already configured library
+settings is simple. As simple as just passing the string to the
+``encrypt()`` and/or ``decrypt()`` methods::
- Decrypts an encoded string. Example::
+ $plain_text = 'This is a plain-text message!';
+ $ciphertext = $this->encryption->encrypt($plain_text);
- $encrypted_string = 'APANtByIGI1BpVXZTJgcsAG8GZl8pdwwa84';
+ // Outputs: This is a plain-text message!
+ echo $this->encryption->decrypt($ciphertext);
- $plaintext_string = $this->encrypt->decode($encrypted_string);
+And that's it! The Encryption library will do everything necessary
+for the whole process to be cryptographically secure out-of-the-box.
+You don't need to worry about it.
- You can optionally pass your encryption key via the second parameter if
- you don't want to use the one in your config file::
+.. important:: Both methods will return FALSE in case of an error.
+ While for ``encrypt()`` this can only mean incorrect
+ configuration, you should always check the return value
+ of ``decrypt()`` in production code.
- $msg = 'My secret message';
- $key = 'super-secret-key';
+How it works
+------------
- $encrypted_string = $this->encrypt->decode($msg, $key);
+If you must know how the process works, here's what happens under
+the hood:
- .. method:: set_cipher($cipher)
+- ``$this->encryption->encrypt($plain_text)``
- :param int $cipher: Valid PHP MCrypt cypher constant
- :returns: CI_Encrypt instance (method chaining)
- :rtype: CI_Encrypt
+ #. Derive an encryption key and a HMAC key from your configured
+ *encryption_key* via HKDF, using the SHA-512 digest algorithm.
+
+ #. Generate a random initialization vector (IV).
- Permits you to set an Mcrypt cipher. By default it uses
- ``MCRYPT_RIJNDAEL_256``. Example::
+ #. Encrypt the data via AES-128 in CBC mode (or another previously
+ configured cipher and mode), using the above-mentioned derived
+ encryption key and IV.
+
+ #. Prepend said IV to the resulting cipher-text.
+
+ #. Base64-encode the resulting string, so that it can be safely
+ stored or transferred without worrying about character sets.
+
+ #. Create a SHA-512 HMAC authentication message using the derived
+ HMAC key to ensure data integrity and prepend it to the Base64
+ string.
- $this->encrypt->set_cipher(MCRYPT_BLOWFISH);
+- ``$this->encryption->decrypt($ciphertext)``
- Please visit php.net for a list of `available ciphers <http://php.net/mcrypt>`_.
+ #. Derive an encryption key and a HMAC key from your configured
+ *encryption_key* via HKDF, using the SHA-512 digest algorithm.
+ Because your configured *encryption_key* is the same, this
+ will produce the same result as in the ``encrypt()`` method
+ above - otherwise you won't be able to decrypt it.
+
+ #. Check if the string is long enough, separate the HMAC out of
+ it and validate if it is correct (this is done in a way that
+ prevents timing attacks agains it). Return FALSE if either of
+ the checks fails.
+
+ #. Base64-decode the string.
+
+ #. Separate the IV out of the cipher-text and decrypt the said
+ cipher-text using that IV and the derived encryption key.
+
+.. _custom-parameters:
+
+Using custom parameters
+-----------------------
+
+Let's say you have to interact with another system that is out
+of your control and uses another method to encrypt data. A
+method that will most certainly not match the above-described
+sequence and probably not use all of the steps either.
- If you'd like to manually test whether your server supports MCrypt you
- can use::
+The Encryption library allows you to change how its encryption
+and decryption processes work, so that you can easily tailor a
+custom solution for such situations.
+
+.. note:: It is possible to use the library in this way, without
+ setting an *encryption_key* in your configuration file.
- echo extension_loaded('mcrypt') ? 'Yup' : 'Nope';
+All you have to do is to pass an associative array with a few
+parameters to either the ``encrypt()`` or ``decrypt()`` method.
+Here's an example::
- .. method:: set_mode($mode)
+ // Assume that we have $ciphertext, $key and $hmac_key
+ // from on outside source
- :param int $mode: Valid PHP MCrypt mode constant
- :returns: CI_Encrypt instance (method chaining)
- :rtype: CI_Encrypt
+ $message = $this->encryption->decrypt(
+ $ciphertext,
+ array(
+ 'cipher' => 'blowfish',
+ 'mode' => 'cbc',
+ 'key' => $key,
+ 'hmac_digest' => 'sha256',
+ 'hmac_key' => $hmac_key
+ )
+ );
+
+In the above example, we are decrypting a message that was encrypted
+using the Blowfish cipher in CBC mode and authenticated via a SHA-256
+HMAC.
+
+.. important:: Note that both 'key' and 'hmac_key' are used in this
+ example. When using custom parameters, encryption and HMAC keys
+ are not derived like the default behavior of the library is.
+
+Below is a list of the available options.
+
+However, unless you really need to and you know what you are doing,
+we advise you to not change the encryption process as this could
+impact security, so please do so with caution.
+
+============= =============== ============================= ======================================================
+Option Default value Mandatory / Optional Description
+============= =============== ============================= ======================================================
+cipher N/A Yes Encryption algorithm (see :ref:`ciphers-and-modes`).
+mode N/A Yes Encryption mode (see :ref:`encryption-modes`).
+key N/A Yes Encryption key.
+iv N/A No Initialization vector (IV).
+ If not provided it will be automatically generated
+ during encryption and looked for during decryption.
+hmac TRUE No Whether to use a HMAC.
+ Boolean. If set to FALSE, then *hmac_digest* and
+ *hmac_key* will be ignored.
+hmac_digest sha512 No HMAC message digest algorithm (see :ref:`digests`).
+hmac_key N/A Yes, unless *hmac* is FALSE HMAC key.
+raw_data FALSE No Whether the cipher-text should be raw.
+ Boolean. If set to TRUE, then Base64 encoding and
+ decoding will not be performed and HMAC will not
+ be a hexadecimal string.
+============= =============== ============================= ======================================================
+
+.. important:: ``encrypt()`` and ``decrypt()`` will return FALSE if
+ a mandatory parameter is not provided or if a provided
+ value is incorrect. This includes *hmac_key*, unless *hmac*
+ is set to FALSE.
+
+.. note:: If GCM mode is used, *hmac* will always be FALSE. This is
+ because GCM mode itself provides authentication.
+
+.. _digests:
+
+Supported HMAC authentication algorithms
+----------------------------------------
+
+For HMAC message authentication, the Encryption library supports
+usage of the SHA-2 family of algorithms:
+
+=========== ==================== ============================
+Algorithm Raw length (bytes) Hex-encoded length (bytes)
+=========== ==================== ============================
+sha512 64 128
+sha384 48 96
+sha256 32 64
+sha224 28 56
+=========== ==================== ============================
+
+The reason for not including other popular algorithms, such as
+MD5 or SHA1 is that they are no longer considered secure enough
+and as such, we don't want to encourage their usage.
+If you absolutely need to use them, it is easy to do so via PHP's
+native `hash_hmac() <http://php.net/hash_hmac()>`_ function.
+
+Stronger algorithms of course will be added in the future as they
+appear and become widely available.
+
+***************
+Class Reference
+***************
+
+.. class:: CI_Encryption
+
+ .. method:: initialize($params)
+
+ :param array $params: Configuration parameters
+ :returns: CI_Encryption instance (method chaining)
+ :rtype: CI_Encryption
+
+ Initializes (configures) the library to use a different
+ driver, cipher, mode or key.
+
+ Example::
+
+ $this->encryption->initialize(
+ array('mode' => 'ctr')
+ );
+
+ Please refer to the :ref:`configuration` section for detailed info.
+
+ .. method:: encrypt($data[, $params = NULL])
+
+ :param string $data: Data to encrypt
+ :param array $params: Optional parameters
+ :returns: Encrypted data or FALSE on failure
+ :rtype: string
+
+ Encrypts the input data and returns its ciphertext.
- Permits you to set an Mcrypt mode. By default it uses **MCRYPT_MODE_CBC**.
Example::
- $this->encrypt->set_mode(MCRYPT_MODE_CFB);
+ $ciphertext = $this->encryption->encrypt('My secret message');
- Please visit php.net for a list of `available modes <http://php.net/mcrypt>`_.
+ Please refer to the :ref:`custom-parameters` section for information
+ on the optional parameters.
- .. method:: encode_from_legacy($string[, $legacy_mode = MCRYPT_MODE_ECB[, $key = '']])
+ .. method:: decrypt($data[, $params = NULL])
- :param string $string: String to encrypt
- :param int $legacy_mode: Valid PHP MCrypt cipher constant
- :param string $key: Encryption key
- :returns: Newly encrypted string
+ :param string $data: Data to decrypt
+ :param array $params: Optional parameters
+ :returns: Decrypted data or FALSE on failure
:rtype: string
- Enables you to re-encode data that was originally encrypted with
- CodeIgniter 1.x to be compatible with the Encryption library in
- CodeIgniter 2.x. It is only necessary to use this method if you have
- encrypted data stored permanently such as in a file or database and are
- on a server that supports Mcrypt. "Light" use encryption such as
- encrypted session data or transitory encrypted flashdata require no
- intervention on your part. However, existing encrypted Sessions will be
- destroyed since data encrypted prior to 2.x will not be decoded.
-
- .. important::
- **Why only a method to re-encode the data instead of maintaining legacy
- methods for both encoding and decoding?** The algorithms in the
- Encryption library have improved in CodeIgniter 2.x both for performance
- and security, and we do not wish to encourage continued use of the older
- methods. You can of course extend the Encryption library if you wish and
- replace the new methods with the old and retain seamless compatibility
- with CodeIgniter 1.x encrypted data, but this a decision that a
- developer should make cautiously and deliberately, if at all.
-
- ::
-
- $new_data = $this->encrypt->encode_from_legacy($old_encrypted_string);
-
- ====================== =============== =======================================================================
- Parameter Default Description
- ====================== =============== =======================================================================
- **$orig_data** n/a The original encrypted data from CodeIgniter 1.x's Encryption library
- **$legacy_mode** MCRYPT_MODE_ECB The Mcrypt mode that was used to generate the original encrypted data.
- CodeIgniter 1.x's default was MCRYPT_MODE_ECB, and it will assume that
- to be the case unless overridden by this parameter.
- **$key** n/a The encryption key. This it typically specified in your config file as
- outlined above.
- ====================== =============== ======================================================================= \ No newline at end of file
+ Decrypts the input data and returns it in plain-text.
+
+ Example::
+
+ echo $this->encryption->decrypt($ciphertext);
+
+ Please refer to the :ref:`custom-parameters` secrion for information
+ on the optional parameters.
+
+ .. method:: hkdf($key[, $digest = 'sha512'[, $salt = NULL[, $length = NULL[, $info = '']]]])
+
+ :param string $key: Input key material
+ :param string $digest: A SHA-2 family digest algorithm
+ :param string $salt: Optional salt
+ :param int $length: Optional output length
+ :param string $info: Optional context/application-specific info
+ :returns: A pseudo-random key or FALSE on failure
+
+ Derives a key from another, presumably weaker key.
+
+ This method is used internally to derive an encryption and HMAC key
+ from your configured *encryption_key*.
+
+ It is publicly available due to its otherwise general purpose. It is
+ described in `RFC 5869 <https://tools.ietf.org/rfc/rfc5869.txt>`_.
+
+ However, as opposed to the description in RFC 5869, this implementation
+ doesn't support SHA1.
+
+ Example::
+
+ $hmac_key = $this->encryption->hkdf(
+ $key,
+ 'sha512',
+ NULL,
+ NULL,
+ 'authentication'
+ );
+
+ // $hmac_key is a pseudo-random key with a length of 64 bytes \ No newline at end of file