password.inc 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291
  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. * Plain-text password up to 512 bytes (128 to 512 UTF-8 characters) 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. // Prevent DoS attacks by refusing to hash large passwords.
  142. if (strlen($password) > 512) {
  143. return FALSE;
  144. }
  145. // The first 12 characters of an existing hash are its setting string.
  146. $setting = substr($setting, 0, 12);
  147. if ($setting[0] != '$' || $setting[2] != '$') {
  148. return FALSE;
  149. }
  150. $count_log2 = _password_get_count_log2($setting);
  151. // Hashes may be imported from elsewhere, so we allow != DRUPAL_HASH_COUNT
  152. if ($count_log2 < DRUPAL_MIN_HASH_COUNT || $count_log2 > DRUPAL_MAX_HASH_COUNT) {
  153. return FALSE;
  154. }
  155. $salt = substr($setting, 4, 8);
  156. // Hashes must have an 8 character salt.
  157. if (strlen($salt) != 8) {
  158. return FALSE;
  159. }
  160. // Convert the base 2 logarithm into an integer.
  161. $count = 1 << $count_log2;
  162. // We rely on the hash() function being available in PHP 5.2+.
  163. $hash = hash($algo, $salt . $password, TRUE);
  164. do {
  165. $hash = hash($algo, $hash . $password, TRUE);
  166. } while (--$count);
  167. $len = strlen($hash);
  168. $output = $setting . _password_base64_encode($hash, $len);
  169. // _password_base64_encode() of a 16 byte MD5 will always be 22 characters.
  170. // _password_base64_encode() of a 64 byte sha512 will always be 86 characters.
  171. $expected = 12 + ceil((8 * $len) / 6);
  172. return (strlen($output) == $expected) ? substr($output, 0, DRUPAL_HASH_LENGTH) : FALSE;
  173. }
  174. /**
  175. * Parse the log2 iteration count from a stored hash or setting string.
  176. */
  177. function _password_get_count_log2($setting) {
  178. $itoa64 = _password_itoa64();
  179. return strpos($itoa64, $setting[3]);
  180. }
  181. /**
  182. * Hash a password using a secure hash.
  183. *
  184. * @param $password
  185. * A plain-text password.
  186. * @param $count_log2
  187. * Optional integer to specify the iteration count. Generally used only during
  188. * mass operations where a value less than the default is needed for speed.
  189. *
  190. * @return
  191. * A string containing the hashed password (and a salt), or FALSE on failure.
  192. */
  193. function user_hash_password($password, $count_log2 = 0) {
  194. if (empty($count_log2)) {
  195. // Use the standard iteration count.
  196. $count_log2 = variable_get('password_count_log2', DRUPAL_HASH_COUNT);
  197. }
  198. return _password_crypt('sha512', $password, _password_generate_salt($count_log2));
  199. }
  200. /**
  201. * Check whether a plain text password matches a stored hashed password.
  202. *
  203. * Alternative implementations of this function may use other data in the
  204. * $account object, for example the uid to look up the hash in a custom table
  205. * or remote database.
  206. *
  207. * @param $password
  208. * A plain-text password
  209. * @param $account
  210. * A user object with at least the fields from the {users} table.
  211. *
  212. * @return
  213. * TRUE or FALSE.
  214. */
  215. function user_check_password($password, $account) {
  216. if (substr($account->pass, 0, 2) == 'U$') {
  217. // This may be an updated password from user_update_7000(). Such hashes
  218. // have 'U' added as the first character and need an extra md5().
  219. $stored_hash = substr($account->pass, 1);
  220. $password = md5($password);
  221. }
  222. else {
  223. $stored_hash = $account->pass;
  224. }
  225. $type = substr($stored_hash, 0, 3);
  226. switch ($type) {
  227. case '$S$':
  228. // A normal Drupal 7 password using sha512.
  229. $hash = _password_crypt('sha512', $password, $stored_hash);
  230. break;
  231. case '$H$':
  232. // phpBB3 uses "$H$" for the same thing as "$P$".
  233. case '$P$':
  234. // A phpass password generated using md5. This is an
  235. // imported password or from an earlier Drupal version.
  236. $hash = _password_crypt('md5', $password, $stored_hash);
  237. break;
  238. default:
  239. return FALSE;
  240. }
  241. return ($hash && $stored_hash == $hash);
  242. }
  243. /**
  244. * Check whether a user's hashed password needs to be replaced with a new hash.
  245. *
  246. * This is typically called during the login process when the plain text
  247. * password is available. A new hash is needed when the desired iteration count
  248. * has changed through a change in the variable password_count_log2 or
  249. * DRUPAL_HASH_COUNT or if the user's password hash was generated in an update
  250. * like user_update_7000().
  251. *
  252. * Alternative implementations of this function might use other criteria based
  253. * on the fields in $account.
  254. *
  255. * @param $account
  256. * A user object with at least the fields from the {users} table.
  257. *
  258. * @return
  259. * TRUE or FALSE.
  260. */
  261. function user_needs_new_hash($account) {
  262. // Check whether this was an updated password.
  263. if ((substr($account->pass, 0, 3) != '$S$') || (strlen($account->pass) != DRUPAL_HASH_LENGTH)) {
  264. return TRUE;
  265. }
  266. // Ensure that $count_log2 is within set bounds.
  267. $count_log2 = _password_enforce_log2_boundaries(variable_get('password_count_log2', DRUPAL_HASH_COUNT));
  268. // Check whether the iteration count used differs from the standard number.
  269. return (_password_get_count_log2($account->pass) !== $count_log2);
  270. }