lithium\storage\session\strategy\Encrypt

class

This strategy allows you to encrypt your Session and / or Cookie data so that it is not stored in cleartext on the client side. You must provide a secret key, otherwise an exception is raised.

To use this class, you need to have the openssl extension enabled.

Example configuration:

Session::config(['default' => [
   'adapter' => 'Cookie',
   'strategies' => ['Encrypt' => ['secret' => 'f00bar$l1thium']]
]]);

By default, this strategy uses the AES algorithm in the CBC mode. This means that an initialization vector has to be generated and transported with the payload data. This is done transparently, but you may want to keep this in mind (the ECB mode doesn't require an initialization vector but is not recommended to use as it's insecure).

Please keep in mind that it is generally not a good idea to store sensitive information in cookies (or generally on the client side) and this class is no exception to the rule. It allows you to store client side data in a more secure way, but 100% security can't be achieved.

Also note that if you provide a secret that is shorter than the maximum key length of the algorithm used, the secret will be hashed to make it more secure. This also means that if you want to use your own hashing algorithm, make sure it has the maximum key length of the algorithm used. See the Encrypt::_hashSecret() method for more information on this.

Legacy Mode

This class previously used the now deprecated mcrypt extension and has been migrated to use of the openssl extension for better support and performance. For backwards compatibility reasons this class supports a legacy mode in which mcrypt will be used. The class will switch to legacy mode whenever it is not possible to use openssl as a drop in.

First, legacy mode will be triggered when the openssl extension is not available.

Second, previously overriding the default cipher and mode were possible (see example below). As we only support AES-256 in CBC mode (equals mcrypt's RIJNDAEL_128 with MODE_CBC) with the openssl extension, overrding the defaults will trigger legacy mode.

Session::config(['default' => [
    'adapter' => 'Cookie',
    'strategies' => ['Encrypt' => [
        'cipher' => MCRYPT_RIJNDAEL_256,
        'mode' => MCRYPT_MODE_ECB, // Don't use ECB when you don't have to!
        'secret' => 'f00bar$l1thium'
    ]]
]]);

Source

class Encrypt extends \lithium\core\ObjectDeprecated {

	/**
	 * Default configuration.
	 */
	protected $_defaults = [
		'secret' => null
	];

	/**
	 * Constructor.
	 *
	 * @param array $config Configuration array.
	 * @return void
	 */
	public function __construct(array $config = []) {
		if (!isset($config['secret'])) {
			throw new ConfigException('Encrypt strategy requires a secret key.');
		}

		if ($this->_mcrypt = $this->_mcrypt($config)) {
			if (!extension_loaded('mcrypt')) {
				throw new ConfigException('The mcrypt extension is not installed or enabled.');
			}
			parent::__construct($config + $this->_defaults + [
				'cipher' => MCRYPT_RIJNDAEL_128,
				'mode' => MCRYPT_MODE_CBC
			]);
			$this->_mcryptResource = mcrypt_module_open(
				$this->_config['cipher'], '', $this->_config['mode'], ''
			);
		} else {
			if (!extension_loaded('openssl')) {
				throw new ConfigException('The `openssl` extension is not installed or enabled.');
			}
			parent::__construct($config + $this->_defaults);
		}
	}

	/**
	 * Read encryption method.
	 *
	 * @param array $data the Data being read.
	 * @param array $options Options for this method.
	 * @return mixed Returns the decrypted data after it was read.
	 */
	public function read($data, array $options = []) {
		$class = $options['class'];

		$encrypted = $class::read(null, ['strategies' => false]);
		$key = isset($options['key']) ? $options['key'] : null;

		if (!isset($encrypted['__encrypted']) || !$encrypted['__encrypted']) {
			return isset($encrypted[$key]) ? $encrypted[$key] : null;
		}

		if ($this->_mcrypt) {
			$current = $this->_bcDecrypt($encrypted['__encrypted']);
		} else {
			$current = $this->_decrypt($encrypted['__encrypted']);
		}

		if ($key) {
			return isset($current[$key]) ? $current[$key] : null;
		}
		return $current;
	}

	/**
	 * Write encryption method.
	 *
	 * @param mixed $data The data to be encrypted.
	 * @param array $options Options for this method.
	 * @return string Returns the encrypted data that was written.
	 */
	public function write($data, array $options = []) {
		$class = $options['class'];

		$futureData = $this->read(null, ['key' => null] + $options) ?: [];
		$futureData = [$options['key'] => $data] + $futureData;

		$payload = null;

		if (!empty($futureData)) {
			if ($this->_mcrypt) {
				$payload = $this->_bcEncrypt($futureData);
			} else {
				$payload = $this->_encrypt($futureData);
			}
		}

		$class::write('__encrypted', $payload, ['strategies' => false] + $options);
		return $payload;
	}

	/**
	 * Delete encryption method.
	 *
	 * @param mixed $data The data to be encrypted.
	 * @param array $options Options for this method.
	 * @return string Returns the deleted data in cleartext.
	 */
	public function delete($data, array $options = []) {
		$class = $options['class'];

		$futureData = $this->read(null, ['key' => null] + $options) ?: [];
		unset($futureData[$options['key']]);

		$payload = null;

		if (!empty($futureData)) {
			if ($this->_mcrypt) {
				$payload = $this->_bcEncrypt($futureData);
			} else {
				$payload = $this->_encrypt($futureData);
			}
		}

		$class::write('__encrypted', $payload, ['strategies' => false] + $options);
		return $data;
	}

	/**
	 * Serialize and encrypt a given data array.
	 *
	 * @param array $decrypted The cleartext data to be encrypted.
	 * @return string A Base64 encoded and encrypted string.
	 */
	protected function _encrypt($decrypted = []) {
		$encrypted = openssl_encrypt(
			serialize($decrypted),
			'aes-256-cbc',
			$this->_hashSecret($this->_config['secret']),
			OPENSSL_RAW_DATA,
			$vector = $this->_vector()
		);
		return base64_encode($encrypted) . base64_encode($vector);
	}

	/**
	 * Decrypt and unserialize a previously encrypted string.
	 *
	 * @param string $encrypted The base64 encoded and encrypted string.
	 * @return array The cleartext data.
	 */
	protected function _decrypt($encrypted) {
		$secret = $this->_hashSecret($this->_config['secret']);

		$vectorSize = strlen(base64_encode(str_repeat(' ', $this->_vectorSize())));
		$vector = base64_decode(substr($encrypted, -$vectorSize));
		$data = base64_decode(substr($encrypted, 0, -$vectorSize));

		$decrypted = openssl_decrypt(
			$data,
			'aes-256-cbc',
			$secret,
			OPENSSL_RAW_DATA|OPENSSL_ZERO_PADDING,
			$vector
		);
		return unserialize(trim($decrypted));
	}

	/**
	 * Determines if the `mcrypt` or `openssl` extension has been installed.
	 *
	 * @return boolean `true` if enabled, `false` otherwise.
	 */
	public static function enabled() {
		return extension_loaded('openssl') || extension_loaded('mcrypt');
	}

	/**
	 * Hashes the given secret to make harder to detect.
	 *
	 * This method figures out the appropriate key size for the chosen encryption algorithm and
	 * then hashes the given key accordingly. Note that if the key has already the needed length,
	 * it is considered to be hashed (secure) already and is therefore not hashed again. This lets
	 * you change the hashing method in your own code if you like.
	 *
	 * The default `aes-256-cbc` key should be 32 byte long `sha256` is used as the
	 * hashing algorithm. If the key size is shorter than the one generated by `sha256`,
	 * the first n bytes will be used.
	 *
	 * @param string $key The possibly too weak key.
	 * @return string The hashed (raw) key.
	 */
	protected function _hashSecret($key) {
		if (strlen($key) >= 32) {
			return $key;
		}
		return substr(hash('sha256', $key, true), 0, 32);
	}

	/**
	 * Generates an initialization vector.
	 *
	 * @return string Returns an initialization vector.
	 */
	protected function _vector() {
		return Random::generate($this->_vectorSize());
	}

	/**
	 * Returns the vector size.
	 *
	 * @return integer The vector size in bytes.
	 */
	protected function _vectorSize() {
		return openssl_cipher_iv_length('aes-256-cbc');
	}

	/* Deprecated / BC */

	/**
	 * Indicates if we are in legacy / BC mode and the class is using the mcrypt extension
	 * or we are able to use the openssl extension.
	 *
	 * @deprecated
	 */
	protected $_mcrypt = false;

	/**
	 * Holds the mcrypt crypto resource after initialization, when in legacy mode.
	 *
	 * @deprecated
	 */
	protected $_mcryptResource = null;

	/**
	 * Checks for legacy mode.
	 *
	 * @deprecated
	 * @param array $config
	 * @return boolean
	 */
	protected function _mcrypt(array $config) {
		if (isset($config['cipher']) || isset($config['mode'])) {
			$message  = "You've selected a non-default cipher and/or mode configuration. ";
			$message .= "The Encrypt strategy is now in legacy mode and will use the ";
			$message .= "deprecated mcrypt extension. To disable legacy mode, use the strategy ";
			$message .= "with default configuration.";
			trigger_error($message, E_USER_DEPRECATED);

			return true;
		}
		if (!extension_loaded('openssl')) {
			$message .= "The Encrypt strategy is now in legacy mode and will use the ";
			$message .= "deprecated mcrypt extension. To disable legacy mode, install the ";
			$message .= "openssl extension.";
			trigger_error($message, E_USER_DEPRECATED);

			return true;
		}
		return false;
	}

	/**
	 * Destructor. Closes the crypto resource when it is no longer needed.
	 *
	 * @deprecated
	 * @return void
	 */
	public function __destruct() {
		if (is_resource($this->_mcryptResource)) {
			mcrypt_module_close($this->_mcryptResource);
		}
	}

	/**
	 * Serialize and encrypt a given data array.
	 *
	 * @deprecated
	 * @param array $decrypted The cleartext data to be encrypted.
	 * @return string A Base64 encoded and encrypted string.
	 */
	protected function _bcEncrypt($decrypted = []) {
		$vector = $this->_bcVector();
		$secret = $this->_bcHashSecret($this->_config['secret']);

		mcrypt_generic_init($this->_mcryptResource, $secret, $vector);
		$encrypted = mcrypt_generic($this->_mcryptResource, serialize($decrypted));
		mcrypt_generic_deinit($this->_mcryptResource);

		return base64_encode($encrypted) . base64_encode($vector);
	}

	/**
	 * Decrypt and unserialize a previously encrypted string.
	 *
	 * @deprecated
	 * @param string $encrypted The base64 encoded and encrypted string.
	 * @return array The cleartext data.
	 */
	protected function _bcDecrypt($encrypted) {
		$secret = $this->_hashSecret($this->_config['secret']);

		$vectorSize = strlen(base64_encode(str_repeat(" ", $this->_bcVectorSize())));
		$vector = base64_decode(substr($encrypted, -$vectorSize));
		$data = base64_decode(substr($encrypted, 0, -$vectorSize));

		mcrypt_generic_init($this->_mcryptResource, $secret, $vector);
		$decrypted = mdecrypt_generic($this->_mcryptResource, $data);
		mcrypt_generic_deinit($this->_mcryptResource);

		return unserialize(trim($decrypted));
	}

	/**
	 * Hashes the given secret to make harder to detect.
	 *
	 * This method figures out the appropriate key size for the chosen encryption algorithm and
	 * then hashes the given key accordingly. Note that if the key has already the needed length,
	 * it is considered to be hashed (secure) already and is therefore not hashed again. This lets
	 * you change the hashing method in your own code if you like.
	 *
	 * The default `MCRYPT_RIJNDAEL_128` key should be 32 byte long `sha256` is used
	 * as the hashing algorithm. If the key size is shorter than the one generated by
	 * `sha256`, the first n bytes will be used.
	 *
	 * @deprecated
	 * @link http://php.net/function.mcrypt-enc-get-key-size.php
	 * @param string $key The possibly too weak key.
	 * @return string The hashed (raw) key.
	 */
	protected function _bcHashSecret($key) {
		$size = mcrypt_enc_get_key_size($this->_mcryptResource);

		if (strlen($key) >= $size) {
			return $key;
		}
		return substr(hash('sha256', $key, true), 0, $size);
	}

	/**
	 * Generates an initialization vector.
	 *
	 * @deprecated
	 * @link http://php.net/function.mcrypt-create-iv.php
	 * @return string Returns an initialization vector.
	 */
	protected function _bcVector() {
		return mcrypt_create_iv($this->_bcVectorSize(), MCRYPT_DEV_URANDOM);
	}

	/**
	 * Returns the vector size vor a given cipher and mode.
	 *
	 * @deprecated
	 * @link http://php.net/function.mcrypt-enc-get-iv-size.php
	 * @return number The vector size.
	 */
	protected function _bcVectorSize() {
		return mcrypt_enc_get_iv_size($this->_mcryptResource);
	}
}