password.inc

Secure password hashing functions for user authentication.

Based on the Portable PHP password hashing framework.

An alternative or custom version of this password hashing API may be used by setting the variable password_inc to the name of the PHP file containing replacement user_hash_password(), user_check_password(), and user_needs_new_hash() functions.

See also

http://www.openwall.com/phpass/

File

drupal-7.x/includes/password.inc
View source
  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. * Encodes 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. }