Merge changes from develop
diff --git a/system/libraries/Encryption.php b/system/libraries/Encryption.php
new file mode 100644
index 0000000..583ddac
--- /dev/null
+++ b/system/libraries/Encryption.php
@@ -0,0 +1,895 @@
+<?php
+/**
+ * CodeIgniter
+ *
+ * An open source application development framework for PHP 5.2.4 or newer
+ *
+ * NOTICE OF LICENSE
+ *
+ * Licensed under the Open Software License version 3.0
+ *
+ * This source file is subject to the Open Software License (OSL 3.0) that is
+ * bundled with this package in the files license.txt / license.rst.  It is
+ * also available through the world wide web at this URL:
+ * http://opensource.org/licenses/OSL-3.0
+ * If you did not receive a copy of the license and are unable to obtain it
+ * through the world wide web, please send an email to
+ * licensing@ellislab.com so we can send you a copy immediately.
+ *
+ * @package		CodeIgniter
+ * @author		EllisLab Dev Team
+ * @copyright	Copyright (c) 2008 - 2013, EllisLab, Inc. (http://ellislab.com/)
+ * @license		http://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0)
+ * @link		http://codeigniter.com
+ * @since		Version 3.0
+ * @filesource
+ */
+defined('BASEPATH') OR exit('No direct script access allowed');
+
+/**
+ * CodeIgniter Encryption Class
+ *
+ * Provides two-way keyed encryption via PHP's MCrypt and/or OpenSSL extensions.
+ *
+ * @package		CodeIgniter
+ * @subpackage	Libraries
+ * @category	Libraries
+ * @author		Andrey Andreev
+ * @link		http://codeigniter.com/user_guide/libraries/encryption.html
+ */
+class CI_Encryption {
+
+	/**
+	 * Encryption cipher
+	 *
+	 * @var	string
+	 */
+	protected $_cipher = 'rijndael-128';
+
+	/**
+	 * Cipher mode
+	 *
+	 * @var	string
+	 */
+	protected $_mode = 'cbc';
+
+	/**
+	 * Cipher handle
+	 *
+	 * @var	mixed
+	 */
+	protected $_handle;
+
+	/**
+	 * Encryption key
+	 *
+	 * @var	string
+	 */
+	protected $_key;
+
+	/**
+	 * PHP extension to be used
+	 *
+	 * @var	string
+	 */
+	protected $_driver;
+
+	/**
+	 * List of usable drivers (PHP extensions)
+	 *
+	 * @var	array
+	 */
+	protected $_drivers = array();
+
+	/**
+	 * List of 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);
+
+		isset($this->_key) OR $this->_key = config_item('encryption_key');
+		if (empty($this->_key))
+		{
+			return show_error('Encryption: You are required to set an encryption key in your configuration.');
+		}
+
+		log_message('debug', 'Encryption Class Initialized');
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Initialize
+	 *
+	 * @param	array	$params	Configuration parameters
+	 * @return	CI_Encryption
+	 */
+	public function initialize(array $params)
+	{
+		if ( ! empty($params['driver']))
+		{
+			if (isset($this->_drivers[$params['driver']]))
+			{
+				if ($this->_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;
+		}
+		elseif ( ! isset($params['key']))
+		{
+			if ( ! isset($this->_key))
+			{
+				return show_error('Encryption: You are required to set an encryption key in your configuration.');
+			}
+
+			$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;
+		}
+		elseif ( ! isset($params['key']))
+		{
+			if ( ! isset($this->_key))
+			{
+				return show_error('Encryption: You are required to set an encryption key in your configuration.');
+			}
+
+			$params['key'] = $this->hkdf($this->_key, 'sha512', NULL, strlen($this->_key), 'encryption');
+		}
+
+		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);
+		}
+
+		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['base64']) ? $params['base64'] : TRUE,
+			'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	$info	Optional context/application-specific info
+	 * @param	$length	Output length (defaults to the selected digest size)
+	 * @return	string	A pseudo-random key
+	 */
+	public function hkdf($key, $digest = 'sha512', $salt = NULL, $length = NULL, $info = '')
+	{
+		if ( ! isset($this->_digests[$digest]))
+		{
+			return FALSE;
+		}
+
+		if (empty($length) OR ! is_int($length))
+		{
+			$length = $this->_digests[$digest];
+		}
+		elseif ($length > (255 * $this->_digests[$digest]))
+		{
+			return FALSE;
+		}
+
+		isset($salt) OR $salt = str_repeat("\0", $this->_digests[$digest]);
+
+		$prk = hash_hmac($digest, $key, $salt, TRUE);
+		$key = '';
+		for ($key_block = '', $block_index = 1; strlen($key) < $length; $block_index++)
+		{
+			$key_block = hash_hmac($digest, $key_block.$info.chr($block_index), $prk, TRUE);
+			$key .= $key_block;
+		}
+
+		return substr($key, 0, $length);
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * __get() magic
+	 *
+	 * @param	string	$key	Property name
+	 * @return	mixed
+	 */
+	public function __get($key)
+	{
+		return in_array($key, array('cipher', 'mode', 'driver', 'drivers', 'digests'), TRUE)
+			? $this->{'_'.$key}
+			: 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 c8dfad6..79712ad 100644
--- a/system/libraries/Session/drivers/Session_cookie.php
+++ b/system/libraries/Session/drivers/Session_cookie.php
@@ -240,7 +240,7 @@
 		// 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 @@
 			return FALSE;
 		}
 
-		$len = strlen($session) - 40;
-
-		if ($len < 0)
-		{
-			log_message('debug', 'The session cookie was not signed.');
-			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++)
-		{
-			$diff |= ord($hmac[$i]) ^ ord($hmac_check[$i]);
-		}
-
-		if ($diff !== 0)
-		{
-			log_message('error', 'The session cookie data did not match what was expected.');
-			$this->sess_destroy();
-			return FALSE;
-		}
-
-		// Check for encryption
 		if ($this->sess_encrypt_cookie === TRUE)
 		{
-			// Decrypt the cookie data
-			$session = $this->CI->encrypt->decode($session);
+			$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;
+			}
+		}
+		else
+		{
+			if (($len = strlen($session) - 40) <= 0)
+			{
+				log_message('error', 'Session: The session cookie was not signed.');
+				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++)
+			{
+				$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 @@
 
 		if ($this->sess_encrypt_cookie === TRUE)
 		{
-			$cookie_data = $this->CI->encrypt->encode($cookie_data);
+			$cookie_data = $this->CI->encryption->encrypt($cookie_data);
 		}
-
-		// Require message authentication
-		$cookie_data .= hash_hmac('sha1', $cookie_data, $this->encryption_key);
+		else
+		{
+			// 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 0000000..41617c2
--- /dev/null
+++ b/tests/codeigniter/libraries/Encryption_test.php
@@ -0,0 +1,186 @@
+<?php
+
+class Encryption_test extends CI_TestCase {
+
+	public function set_up()
+	{
+		$this->ci_set_config('encryption_key', "\xd0\xc9\x08\xc4\xde\x52\x12\x6e\xf8\xcc\xdb\x03\xea\xa0\x3a\x5c");
+		$this->encryption = new CI_Encryption();
+		$this->ci_instance_var('encryption', $this->encryption);
+	}
+
+	// --------------------------------------------------------------------
+
+	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));
+		}
+	}
+
+	// --------------------------------------------------------------------
+
+	public function test_hkdf()
+	{
+		// Test vectors are described in RFC5869, Appendix A(1-3).
+		// Vectors 4-7 cover SHA-1, which we don't support.
+		//
+		// URL: https://tools.ietf.org/rfc/rfc5869.txt
+		//
+		// Our implementation doesn't split into hkdf_extract(), hkdf_expand()
+		// and therefore we can't test for the PRK value (it is included below
+		// just for consistency, hence why it's also commented out).
+		//
+		// As long as OKM is correct, then we're all fine though.
+		$vectors = array(
+			// Appendix 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"
+			),
+			// Appendix 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",
+			),
+			// Appendix 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));
+	}
+
+}
\ No newline at end of file
diff --git a/tests/mocks/autoloader.php b/tests/mocks/autoloader.php
index cc0a2e2..1bcde79 100644
--- a/tests/mocks/autoloader.php
+++ b/tests/mocks/autoloader.php
@@ -14,26 +14,49 @@
 	$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/user_guide_src/source/changelog.rst b/user_guide_src/source/changelog.rst
index a5839a2..d9f77c6 100644
--- a/user_guide_src/source/changelog.rst
+++ b/user_guide_src/source/changelog.rst
@@ -257,6 +257,14 @@
 
 -  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.
@@ -358,11 +366,6 @@
       -  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.
@@ -574,7 +577,7 @@
 -  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>`'s ``join()`` method only escaped one set of conditions.
 -  Fixed a bug (#1321) - Core Exceptions class couldn't find the errors/ folder 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>` method ``encode_from_legacy()`` didn't set back the encrypt mode on failure.
 -  Fixed a bug (#145) - compile_binds() failed when the bind marker was present in a literal string within the query.
 -  Fixed a bug in 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.
@@ -1290,7 +1293,7 @@
    -  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:`Encryption 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 29f44bd..948b1bc 100644
--- a/user_guide_src/source/installation/upgrade_200.rst
+++ b/user_guide_src/source/installation/upgrade_200.rst
@@ -50,11 +50,11 @@
 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 @@
 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 88bb111..a88d34e 100644
--- a/user_guide_src/source/installation/upgrade_300.rst
+++ b/user_guide_src/source/installation/upgrade_300.rst
@@ -318,7 +318,7 @@
 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 @@
 :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/encryption.rst b/user_guide_src/source/libraries/encrypt.rst
similarity index 100%
rename from user_guide_src/source/libraries/encryption.rst
rename to user_guide_src/source/libraries/encrypt.rst