password.inc 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287
  1. <?php
  2. /**
  3. * @file
  4. * Secure password hashing functions for user authentication.
  5. *
  6. * Based on the Portable PHP password hashing framework.
  7. * @see http://www.openwall.com/phpass/
  8. *
  9. * An alternative or custom version of this password hashing API may be
  10. * used by setting the variable password_inc to the name of the PHP file
  11. * containing replacement user_hash_password(), user_check_password(), and
  12. * user_needs_new_hash() functions.
  13. */
  14. /**
  15. * The standard log2 number of iterations for password stretching. This should
  16. * increase by 1 every Drupal version in order to counteract increases in the
  17. * speed and power of computers available to crack the hashes.
  18. */
  19. define('DRUPAL_HASH_COUNT', 15);
  20. /**
  21. * The minimum allowed log2 number of iterations for password stretching.
  22. */
  23. define('DRUPAL_MIN_HASH_COUNT', 7);
  24. /**
  25. * The maximum allowed log2 number of iterations for password stretching.
  26. */
  27. define('DRUPAL_MAX_HASH_COUNT', 30);
  28. /**
  29. * The expected (and maximum) number of characters in a hashed password.
  30. */
  31. define('DRUPAL_HASH_LENGTH', 55);
  32. /**
  33. * Returns a string for mapping an int to the corresponding base 64 character.
  34. */
  35. function _password_itoa64() {
  36. return './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
  37. }
  38. /**
  39. * Encode bytes into printable base 64 using the *nix standard from crypt().
  40. *
  41. * @param $input
  42. * The string containing bytes to encode.
  43. * @param $count
  44. * The number of characters (bytes) to encode.
  45. *
  46. * @return
  47. * Encoded string
  48. */
  49. function _password_base64_encode($input, $count) {
  50. $output = '';
  51. $i = 0;
  52. $itoa64 = _password_itoa64();
  53. do {
  54. $value = ord($input[$i++]);
  55. $output .= $itoa64[$value & 0x3f];
  56. if ($i < $count) {
  57. $value |= ord($input[$i]) << 8;
  58. }
  59. $output .= $itoa64[($value >> 6) & 0x3f];
  60. if ($i++ >= $count) {
  61. break;
  62. }
  63. if ($i < $count) {
  64. $value |= ord($input[$i]) << 16;
  65. }
  66. $output .= $itoa64[($value >> 12) & 0x3f];
  67. if ($i++ >= $count) {
  68. break;
  69. }
  70. $output .= $itoa64[($value >> 18) & 0x3f];
  71. } while ($i < $count);
  72. return $output;
  73. }
  74. /**
  75. * Generates a random base 64-encoded salt prefixed with settings for the hash.
  76. *
  77. * Proper use of salts may defeat a number of attacks, including:
  78. * - The ability to try candidate passwords against multiple hashes at once.
  79. * - The ability to use pre-hashed lists of candidate passwords.
  80. * - The ability to determine whether two users have the same (or different)
  81. * password without actually having to guess one of the passwords.
  82. *
  83. * @param $count_log2
  84. * Integer that determines the number of iterations used in the hashing
  85. * process. A larger value is more secure, but takes more time to complete.
  86. *
  87. * @return
  88. * A 12 character string containing the iteration count and a random salt.
  89. */
  90. function _password_generate_salt($count_log2) {
  91. $output = '$S$';
  92. // Ensure that $count_log2 is within set bounds.
  93. $count_log2 = _password_enforce_log2_boundaries($count_log2);
  94. // We encode the final log2 iteration count in base 64.
  95. $itoa64 = _password_itoa64();
  96. $output .= $itoa64[$count_log2];
  97. // 6 bytes is the standard salt for a portable phpass hash.
  98. $output .= _password_base64_encode(drupal_random_bytes(6), 6);
  99. return $output;
  100. }
  101. /**
  102. * Ensures that $count_log2 is within set bounds.
  103. *
  104. * @param $count_log2
  105. * Integer that determines the number of iterations used in the hashing
  106. * process. A larger value is more secure, but takes more time to complete.
  107. *
  108. * @return
  109. * Integer within set bounds that is closest to $count_log2.
  110. */
  111. function _password_enforce_log2_boundaries($count_log2) {
  112. if ($count_log2 < DRUPAL_MIN_HASH_COUNT) {
  113. return DRUPAL_MIN_HASH_COUNT;
  114. }
  115. elseif ($count_log2 > DRUPAL_MAX_HASH_COUNT) {
  116. return DRUPAL_MAX_HASH_COUNT;
  117. }
  118. return (int) $count_log2;
  119. }
  120. /**
  121. * Hash a password using a secure stretched hash.
  122. *
  123. * By using a salt and repeated hashing the password is "stretched". Its
  124. * security is increased because it becomes much more computationally costly
  125. * for an attacker to try to break the hash by brute-force computation of the
  126. * hashes of a large number of plain-text words or strings to find a match.
  127. *
  128. * @param $algo
  129. * The string name of a hashing algorithm usable by hash(), like 'sha256'.
  130. * @param $password
  131. * The plain-text password to hash.
  132. * @param $setting
  133. * An existing hash or the output of _password_generate_salt(). Must be
  134. * at least 12 characters (the settings and salt).
  135. *
  136. * @return
  137. * A string containing the hashed password (and salt) or FALSE on failure.
  138. * The return string will be truncated at DRUPAL_HASH_LENGTH characters max.
  139. */
  140. function _password_crypt($algo, $password, $setting) {
  141. // The first 12 characters of an existing hash are its setting string.
  142. $setting = substr($setting, 0, 12);
  143. if ($setting[0] != '$' || $setting[2] != '$') {
  144. return FALSE;
  145. }
  146. $count_log2 = _password_get_count_log2($setting);
  147. // Hashes may be imported from elsewhere, so we allow != DRUPAL_HASH_COUNT
  148. if ($count_log2 < DRUPAL_MIN_HASH_COUNT || $count_log2 > DRUPAL_MAX_HASH_COUNT) {
  149. return FALSE;
  150. }
  151. $salt = substr($setting, 4, 8);
  152. // Hashes must have an 8 character salt.
  153. if (strlen($salt) != 8) {
  154. return FALSE;
  155. }
  156. // Convert the base 2 logarithm into an integer.
  157. $count = 1 << $count_log2;
  158. // We rely on the hash() function being available in PHP 5.2+.
  159. $hash = hash($algo, $salt . $password, TRUE);
  160. do {
  161. $hash = hash($algo, $hash . $password, TRUE);
  162. } while (--$count);
  163. $len = strlen($hash);
  164. $output = $setting . _password_base64_encode($hash, $len);
  165. // _password_base64_encode() of a 16 byte MD5 will always be 22 characters.
  166. // _password_base64_encode() of a 64 byte sha512 will always be 86 characters.
  167. $expected = 12 + ceil((8 * $len) / 6);
  168. return (strlen($output) == $expected) ? substr($output, 0, DRUPAL_HASH_LENGTH) : FALSE;
  169. }
  170. /**
  171. * Parse the log2 iteration count from a stored hash or setting string.
  172. */
  173. function _password_get_count_log2($setting) {
  174. $itoa64 = _password_itoa64();
  175. return strpos($itoa64, $setting[3]);
  176. }
  177. /**
  178. * Hash a password using a secure hash.
  179. *
  180. * @param $password
  181. * A plain-text password.
  182. * @param $count_log2
  183. * Optional integer to specify the iteration count. Generally used only during
  184. * mass operations where a value less than the default is needed for speed.
  185. *
  186. * @return
  187. * A string containing the hashed password (and a salt), or FALSE on failure.
  188. */
  189. function user_hash_password($password, $count_log2 = 0) {
  190. if (empty($count_log2)) {
  191. // Use the standard iteration count.
  192. $count_log2 = variable_get('password_count_log2', DRUPAL_HASH_COUNT);
  193. }
  194. return _password_crypt('sha512', $password, _password_generate_salt($count_log2));
  195. }
  196. /**
  197. * Check whether a plain text password matches a stored hashed password.
  198. *
  199. * Alternative implementations of this function may use other data in the
  200. * $account object, for example the uid to look up the hash in a custom table
  201. * or remote database.
  202. *
  203. * @param $password
  204. * A plain-text password
  205. * @param $account
  206. * A user object with at least the fields from the {users} table.
  207. *
  208. * @return
  209. * TRUE or FALSE.
  210. */
  211. function user_check_password($password, $account) {
  212. if (substr($account->pass, 0, 2) == 'U$') {
  213. // This may be an updated password from user_update_7000(). Such hashes
  214. // have 'U' added as the first character and need an extra md5().
  215. $stored_hash = substr($account->pass, 1);
  216. $password = md5($password);
  217. }
  218. else {
  219. $stored_hash = $account->pass;
  220. }
  221. $type = substr($stored_hash, 0, 3);
  222. switch ($type) {
  223. case '$S$':
  224. // A normal Drupal 7 password using sha512.
  225. $hash = _password_crypt('sha512', $password, $stored_hash);
  226. break;
  227. case '$H$':
  228. // phpBB3 uses "$H$" for the same thing as "$P$".
  229. case '$P$':
  230. // A phpass password generated using md5. This is an
  231. // imported password or from an earlier Drupal version.
  232. $hash = _password_crypt('md5', $password, $stored_hash);
  233. break;
  234. default:
  235. return FALSE;
  236. }
  237. return ($hash && $stored_hash == $hash);
  238. }
  239. /**
  240. * Check whether a user's hashed password needs to be replaced with a new hash.
  241. *
  242. * This is typically called during the login process when the plain text
  243. * password is available. A new hash is needed when the desired iteration count
  244. * has changed through a change in the variable password_count_log2 or
  245. * DRUPAL_HASH_COUNT or if the user's password hash was generated in an update
  246. * like user_update_7000().
  247. *
  248. * Alternative implementations of this function might use other criteria based
  249. * on the fields in $account.
  250. *
  251. * @param $account
  252. * A user object with at least the fields from the {users} table.
  253. *
  254. * @return
  255. * TRUE or FALSE.
  256. */
  257. function user_needs_new_hash($account) {
  258. // Check whether this was an updated password.
  259. if ((substr($account->pass, 0, 3) != '$S$') || (strlen($account->pass) != DRUPAL_HASH_LENGTH)) {
  260. return TRUE;
  261. }
  262. // Ensure that $count_log2 is within set bounds.
  263. $count_log2 = _password_enforce_log2_boundaries(variable_get('password_count_log2', DRUPAL_HASH_COUNT));
  264. // Check whether the iteration count used differs from the standard number.
  265. return (_password_get_count_log2($account->pass) !== $count_log2);
  266. }