Encrypt.php 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270
  1. <?php
  2. /**
  3. * Lithium: the most rad php framework
  4. *
  5. * @copyright Copyright 2013, Union of RAD (http://union-of-rad.org)
  6. * @license http://opensource.org/licenses/bsd-license.php The BSD License
  7. */
  8. namespace lithium\storage\session\strategy;
  9. use lithium\core\ConfigException;
  10. /**
  11. * This strategy allows you to encrypt your `Session` and / or `Cookie` data so that it
  12. * is not stored in cleartext on the client side. You must provide a secret key, otherwise
  13. * an exception is raised.
  14. *
  15. * To use this class, you need to have the `mcrypt` extension enabled.
  16. *
  17. * Example configuration:
  18. *
  19. * {{{
  20. * Session::config(array('default' => array(
  21. * 'adapter' => 'Cookie',
  22. * 'strategies' => array('Encrypt' => array('secret' => 'f00bar$l1thium'))
  23. * )));
  24. * }}}
  25. *
  26. * By default, this strategy uses the AES algorithm in the CBC mode. This means that an
  27. * initialization vector has to be generated and transported with the payload data. This
  28. * is done transparently, but you may want to keep this in mind (the ECB mode doesn't require
  29. * an initialization vector but is not recommended to use as it's insecure). You can override this
  30. * defaults by passing a different `cipher` and/or `mode` to the config like this:
  31. *
  32. * {{{
  33. * Session::config(array('default' => array(
  34. * 'adapter' => 'Cookie',
  35. * 'strategies' => array('Encrypt' => array(
  36. * 'cipher' => MCRYPT_RIJNDAEL_256,
  37. * 'mode' => MCRYPT_MODE_ECB, // Don't use ECB when you don't have to!
  38. * 'secret' => 'f00bar$l1thium'
  39. * ))
  40. * )));
  41. * }}}
  42. *
  43. * Please keep in mind that it is generally not a good idea to store sensitive information in
  44. * cookies (or generally on the client side) and this class is no exception to the rule. It allows
  45. * you to store client side data in a more secure way, but 100% security can't be achieved.
  46. *
  47. * Also note that if you provide a secret that is shorter than the maximum key length of the
  48. * algorithm used, the secret will be hashed to make it more secure. This also means that if you
  49. * want to use your own hashing algorithm, make sure it has the maximum key length of the algorithm
  50. * used. See the `Encrypt::_hashSecret()` method for more information on this.
  51. *
  52. * @link http://php.net/manual/en/book.mcrypt.php The mcrypt extension.
  53. * @link http://www.php.net/manual/en/mcrypt.ciphers.php List of supported ciphers.
  54. * @link http://www.php.net/manual/en/mcrypt.constants.php List of supported modes.
  55. */
  56. class Encrypt extends \lithium\core\Object {
  57. /**
  58. * Holds the initialization vector.
  59. */
  60. protected static $_vector = null;
  61. /**
  62. * Holds the crypto resource after initialization.
  63. */
  64. protected static $_resource = null;
  65. /**
  66. * Default configuration.
  67. */
  68. protected $_defaults = array(
  69. 'cipher' => MCRYPT_RIJNDAEL_128,
  70. 'mode' => MCRYPT_MODE_CBC
  71. );
  72. /**
  73. * Constructor.
  74. *
  75. * @param array $config Configuration array. You can override the default cipher and mode.
  76. */
  77. public function __construct(array $config = array()) {
  78. if (!static::enabled()) {
  79. throw new ConfigException("The Mcrypt extension is not installed or enabled.");
  80. }
  81. if (!isset($config['secret'])) {
  82. throw new ConfigException("Encrypt strategy requires a secret key.");
  83. }
  84. parent::__construct($config + $this->_defaults);
  85. $cipher = $this->_config['cipher'];
  86. $mode = $this->_config['mode'];
  87. static::$_resource = mcrypt_module_open($cipher, '', $mode, '');
  88. $this->_config['vector'] = static::_vector();
  89. }
  90. /**
  91. * Destructor.
  92. *
  93. * Closes the crypto resource when it is no longer needed.
  94. */
  95. public function __destruct() {
  96. mcrypt_module_close(static::$_resource);
  97. }
  98. /**
  99. * Read encryption method.
  100. *
  101. * @param array $data the Data being read.
  102. * @param array $options Options for this method.
  103. * @return mixed Returns the decrypted key or the dataset.
  104. */
  105. public function read($data, array $options = array()) {
  106. $class = $options['class'];
  107. $encrypted = $class::read(null, array('strategies' => false));
  108. $key = isset($options['key']) ? $options['key'] : null;
  109. if (!isset($encrypted['__encrypted']) || !$encrypted['__encrypted']) {
  110. return isset($encrypted[$key]) ? $encrypted[$key] : null;
  111. }
  112. $current = $this->_decrypt($encrypted['__encrypted']);
  113. if ($key) {
  114. return isset($current[$key]) ? $current[$key] : null;
  115. } else {
  116. return $current;
  117. }
  118. }
  119. /**
  120. * Write encryption method.
  121. *
  122. * @param mixed $data The data to be encrypted.
  123. * @param array $options Options for this method.
  124. * @return string Returns the written data in cleartext.
  125. */
  126. public function write($data, array $options = array()) {
  127. $class = $options['class'];
  128. $futureData = $this->read(null, array('key' => null) + $options) ?: array();
  129. $futureData = array($options['key'] => $data) + $futureData;
  130. $payload = empty($futureData) ? null : $this->_encrypt($futureData);
  131. $class::write('__encrypted', $payload, array('strategies' => false) + $options);
  132. return $payload;
  133. }
  134. /**
  135. * Delete encryption method.
  136. *
  137. * @param mixed $data The data to be encrypted.
  138. * @param array $options Options for this method.
  139. * @return string Returns the deleted data in cleartext.
  140. */
  141. public function delete($data, array $options = array()) {
  142. $class = $options['class'];
  143. $futureData = $this->read(null, array('key' => null) + $options) ?: array();
  144. unset($futureData[$options['key']]);
  145. $payload = empty($futureData) ? null : $this->_encrypt($futureData);
  146. $class::write('__encrypted', $payload, array('strategies' => false) + $options);
  147. return $data;
  148. }
  149. /**
  150. * Serialize and encrypt a given data array.
  151. *
  152. * @param array $decrypted The cleartext data to be encrypted.
  153. * @return string A Base64 encoded and encrypted string.
  154. */
  155. protected function _encrypt($decrypted = array()) {
  156. $vector = $this->_config['vector'];
  157. $secret = $this->_hashSecret($this->_config['secret']);
  158. mcrypt_generic_init(static::$_resource, $secret, $vector);
  159. $encrypted = mcrypt_generic(static::$_resource, serialize($decrypted));
  160. mcrypt_generic_deinit(static::$_resource);
  161. return base64_encode($encrypted) . base64_encode($vector);
  162. }
  163. /**
  164. * Decrypt and unserialize a previously encrypted string.
  165. *
  166. * @param string $encrypted The base64 encoded and encrypted string.
  167. * @return array The cleartext data.
  168. */
  169. protected function _decrypt($encrypted) {
  170. $secret = $this->_hashSecret($this->_config['secret']);
  171. $vectorSize = strlen(base64_encode(str_repeat(" ", static::_vectorSize())));
  172. $vector = base64_decode(substr($encrypted, -$vectorSize));
  173. $data = base64_decode(substr($encrypted, 0, -$vectorSize));
  174. mcrypt_generic_init(static::$_resource, $secret, $vector);
  175. $decrypted = mdecrypt_generic(static::$_resource, $data);
  176. mcrypt_generic_deinit(static::$_resource);
  177. return unserialize(trim($decrypted));
  178. }
  179. /**
  180. * Determines if the Mcrypt extension has been installed.
  181. *
  182. * @return boolean `true` if enabled, `false` otherwise.
  183. */
  184. public static function enabled() {
  185. return extension_loaded('mcrypt');
  186. }
  187. /**
  188. * Hashes the given secret to make harder to detect.
  189. *
  190. * This method figures out the appropriate key size for the chosen encryption algorithm and
  191. * then hashes the given key accordingly. Note that if the key has already the needed length,
  192. * it is considered to be hashed (secure) already and is therefore not hashed again. This lets
  193. * you change the hashing method in your own code if you like.
  194. *
  195. * The default `MCRYPT_RIJNDAEL_128` key should be 32 byte long `sha256` is used as the hashing
  196. * algorithm. If the key size is shorter than the one generated by `sha256`, the first n bytes
  197. * will be used.
  198. *
  199. * @link http://www.php.net/manual/de/function.mcrypt-enc-get-key-size.php
  200. * @param string $key The possibly too weak key.
  201. * @return string The hashed (raw) key.
  202. */
  203. protected function _hashSecret($key) {
  204. $size = mcrypt_enc_get_key_size(static::$_resource);
  205. if (strlen($key) >= $size) {
  206. return $key;
  207. }
  208. return substr(hash('sha256', $key, true), 0, $size);
  209. }
  210. /**
  211. * Generates an initialization vector.
  212. *
  213. * @return string Returns an initialization vector.
  214. * @link http://www.php.net/manual/en/function.mcrypt-create-iv.php
  215. */
  216. protected static function _vector() {
  217. if (static::$_vector) {
  218. return static::$_vector;
  219. }
  220. return static::$_vector = mcrypt_create_iv(static::_vectorSize(), MCRYPT_DEV_URANDOM);
  221. }
  222. /**
  223. * Returns the vector size vor a given cipher and mode.
  224. *
  225. * @return number The vector size.
  226. * @link http://www.php.net/manual/en/function.mcrypt-enc-get-iv-size.php
  227. */
  228. protected static function _vectorSize() {
  229. return mcrypt_enc_get_iv_size(static::$_resource);
  230. }
  231. }
  232. ?>