PageRenderTime 56ms CodeModel.GetById 28ms RepoModel.GetById 0ms app.codeStats 0ms

/security/Password.php

http://github.com/UnionOfRAD/lithium
PHP | 238 lines | 68 code | 22 blank | 148 comment | 9 complexity | 08567d4ba44eeb10c0441ef900226d04 MD5 | raw file
  1. <?php
  2. /**
  3. * li₃: the most RAD framework for PHP (http://li3.me)
  4. *
  5. * Copyright 2010, Union of RAD. All rights reserved. This source
  6. * code is distributed under the terms of the BSD 3-Clause License.
  7. * The full license text can be found in the LICENSE.txt file.
  8. */
  9. namespace lithium\security;
  10. use lithium\security\Hash;
  11. use lithium\security\Random;
  12. /**
  13. * `Password` utility class that makes use of PHP's `crypt()` function. Includes a
  14. * cryptographically strong salt generator, and utility functions to hash and check
  15. * passwords.
  16. */
  17. class Password {
  18. /**
  19. * The default log2 number of iterations for Blowfish encryption.
  20. */
  21. const BF = 10;
  22. /**
  23. * The default log2 number of iterations for XDES encryption.
  24. */
  25. const XDES = 18;
  26. /**
  27. * Hashes a password using PHP's `crypt()` and an optional salt. If no
  28. * salt is supplied, a cryptographically strong salt will be generated
  29. * using `lithium\security\Password::salt()`.
  30. *
  31. * Using this function is the proper way to hash a password. Using naïve
  32. * methods such as sha1 or md5, as is done in many web applications, is
  33. * improper due to the lack of a cryptographically strong salt.
  34. *
  35. * Using `lithium\security\Password::hash()` ensures that:
  36. *
  37. * - Two identical passwords will never use the same salt, thus never
  38. * resulting in the same hash; this prevents a potential attacker from
  39. * compromising user accounts by using a database of most commonly used
  40. * passwords.
  41. * - The salt generator's count iterator can be increased within Lithium
  42. * or your application as computer hardware becomes faster; this results
  43. * in slower hash generation, without invalidating existing passwords.
  44. *
  45. * Usage:
  46. *
  47. * ```
  48. * // Hash a password before storing it:
  49. * $hashed = Password::hash($password);
  50. *
  51. * // Check a password by comparing it to its hashed value:
  52. * $check = Password::check($password, $hashed);
  53. *
  54. * // Use a stronger custom salt:
  55. * $salt = Password::salt('bf', 16); // 2^16 iterations
  56. * $hashed = Password::hash($password, $salt); // Very slow
  57. * $check = Password::check($password, $hashed); // Very slow
  58. *
  59. * // Forward/backward compatibility
  60. * $salt1 = Password::salt('bf', 6);
  61. * $salt2 = Password::salt('bf', 12);
  62. * $hashed1 = Password::hash($password, $salt1); // Fast
  63. * $hashed2 = Password::hash($password, $salt2); // Slow
  64. * $check1 = Password::check($password, $hashed1); // True
  65. * $check2 = Password::check($password, $hashed2); // True
  66. * ```
  67. *
  68. * @see lithium\security\Password::check()
  69. * @see lithium\security\Password::salt()
  70. * @link http://php.net/manual/function.crypt.php
  71. * @param string $password The password to hash.
  72. * @param string $salt Optional. The salt string.
  73. * @return string The hashed password.
  74. * The result's length will be:
  75. * - 60 chars long for Blowfish hashes
  76. * - 20 chars long for XDES hashes
  77. * - 34 chars long for MD5 hashes
  78. */
  79. public static function hash($password, $salt = null) {
  80. return crypt($password, $salt ?: static::salt());
  81. }
  82. /**
  83. * Compares a password and its hashed value using PHP's `crypt()`. Rather than a simple string
  84. * comparison, this method uses a constant-time algorithm to defend against timing attacks.
  85. *
  86. * @see lithium\security\Password::hash()
  87. * @see lithium\security\Password::salt()
  88. * @param string $password The user-supplied plaintext password to check.
  89. * @param string $hash The known hashed password to compare it to.
  90. * @return boolean Returns a boolean indicating whether the password is correct.
  91. */
  92. public static function check($password, $hash) {
  93. return Hash::compare($hash, crypt($password, $hash));
  94. }
  95. /**
  96. * Generates a cryptographically strong salt, using the best available
  97. * method (tries Blowfish, then XDES, and fallbacks to MD5), for use in
  98. * `Password::hash()`.
  99. *
  100. * Blowfish and XDES are adaptive hashing algorithms. MD5 is not. Adaptive
  101. * hashing algorithms are designed in such a way that when computers get
  102. * faster, you can tune the algorithm to be slower by increasing the number
  103. * of hash iterations, without introducing incompatibility with existing
  104. * passwords.
  105. *
  106. * To pick an appropriate iteration count for adaptive algorithms, consider
  107. * that the original DES crypt was designed to have the speed of 4 hashes
  108. * per second on the hardware of that time. Slower than 4 hashes per second
  109. * would probably dampen usability. Faster than 100 hashes per second is
  110. * probably too fast. The defaults generate about 10 hashes per second
  111. * using a dual-core 2.2GHz CPU.
  112. *
  113. * _Note 1_: this salt generator is different from naive salt implementations
  114. * (e.g. `md5(microtime())`) in that it uses all of the available bits of
  115. * entropy for the supplied salt method.
  116. *
  117. * _Note2_: this method should not be use to generate custom salts. Indeed,
  118. * the resulting salts are prefixed with information expected by PHP's
  119. * `crypt()`. To get an arbitrarily long, cryptographically strong salt
  120. * consisting in random sequences of alpha numeric characters, use
  121. * `lithium\security\Random::generate()` instead.
  122. *
  123. * @link http://php.net/function.crypt.php
  124. * @link http://www.postgresql.org/docs/9.0/static/pgcrypto.html
  125. * @see lithium\security\Password::hash()
  126. * @see lithium\security\Password::check()
  127. * @see lithium\security\Random::generate()
  128. * @param string $type The hash type. Optional. Defaults to the best
  129. * available option. Supported values, along with their maximum
  130. * password lengths, include:
  131. * - `'bf'`: Blowfish (128 salt bits, max 72 chars)
  132. * - `'xdes'`: XDES (24 salt bits, max 8 chars)
  133. * - `'md5'`: MD5 (48 salt bits, unlimited length)
  134. * @param integer $count Optional. The base-2 logarithm of the iteration
  135. * count, for adaptive algorithms. Defaults to:
  136. * - `10` for Blowfish
  137. * - `18` for XDES
  138. * @return string The salt string.
  139. */
  140. public static function salt($type = null, $count = null) {
  141. switch (true) {
  142. case CRYPT_BLOWFISH === 1 && (!$type || $type === 'bf'):
  143. return static::_generateSaltBf($count);
  144. case CRYPT_EXT_DES === 1 && (!$type || $type === 'xdes'):
  145. return static::_generateSaltXdes($count);
  146. default:
  147. return static::_generateSaltMd5();
  148. }
  149. }
  150. /**
  151. * Generates a Blowfish salt for use in `lithium\security\Password::hash()`. _Note_: Does not
  152. * use the `'encode'` option of `Random::generate()` because it could result in 2 bits less of
  153. * entropy depending on the last character.
  154. *
  155. * @param integer $count The base-2 logarithm of the iteration count.
  156. * Defaults to `10`. Can be `4` to `31`.
  157. * @return string The Blowfish salt.
  158. */
  159. protected static function _generateSaltBf($count = null) {
  160. $count = (integer) $count;
  161. $count = ($count < 4 || $count > 31) ? static::BF : $count;
  162. $base64 = './ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
  163. $i = 0;
  164. $input = Random::generate(16);
  165. $output = '';
  166. do {
  167. $c1 = ord($input[$i++]);
  168. $output .= $base64[$c1 >> 2];
  169. $c1 = ($c1 & 0x03) << 4;
  170. if ($i >= 16) {
  171. $output .= $base64[$c1];
  172. break;
  173. }
  174. $c2 = ord($input[$i++]);
  175. $c1 |= $c2 >> 4;
  176. $output .= $base64[$c1];
  177. $c1 = ($c2 & 0x0f) << 2;
  178. $c2 = ord($input[$i++]);
  179. $c1 |= $c2 >> 6;
  180. $output .= $base64[$c1];
  181. $output .= $base64[$c2 & 0x3f];
  182. } while (1);
  183. $result = '$2a$';
  184. $result .= chr(ord('0') + $count / static::BF);
  185. $result .= chr(ord('0') + $count % static::BF);
  186. $result .= '$' . $output;
  187. return $result;
  188. }
  189. /**
  190. * Generates an Extended DES salt for use in `lithium\security\Password::hash()`.
  191. *
  192. * @param integer $count The base-2 logarithm of the iteration count. Defaults to `18`. Can be
  193. * `1` to `24`. 1 will be stripped from the non-log value, e.g. 2^18 - 1, to
  194. * ensure we don't use a weak DES key.
  195. * @return string The XDES salt.
  196. */
  197. protected static function _generateSaltXdes($count = null) {
  198. $count = (integer) $count;
  199. $count = ($count < 1 || $count > 24) ? static::XDES : $count;
  200. $count = (1 << $count) - 1;
  201. $base64 = './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
  202. $output = '_' . $base64[$count & 0x3f] . $base64[($count >> 6) & 0x3f];
  203. $output .= $base64[($count >> 12) & 0x3f] . $base64[($count >> 18) & 0x3f];
  204. $output .= Random::generate(3, ['encode' => Random::ENCODE_BASE_64]);
  205. return $output;
  206. }
  207. /**
  208. * Generates an MD5 salt for use in `lithium\security\Password::hash()`.
  209. *
  210. * @return string The MD5 salt.
  211. */
  212. protected static function _generateSaltMd5() {
  213. return '$1$' . Random::generate(6, ['encode' => Random::ENCODE_BASE_64]);
  214. }
  215. }
  216. ?>