password.inc

  1. 6.x includes/password.inc
  2. 5.x includes/password.inc

Secure password hashing functions for user authentication.

Please NOTE: This file was taken largely from the open source Drupal 7 CMS project, and is used here in compliance with the GNU GPL v.3+ license.

For more documentation, see: https://api.drupal.org/api/drupal/includes%21password.inc/7

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

includes/password.inc
View source
  1. <?php
  2. /**
  3. * @file
  4. * Secure password hashing functions for user authentication.
  5. *
  6. *
  7. * Please NOTE: This file was taken largely from the open source Drupal 7 CMS project,
  8. * and is used here in compliance with the GNU GPL v.3+ license.
  9. *
  10. * For more documentation, see:
  11. * https://api.drupal.org/api/drupal/includes%21password.inc/7
  12. *
  13. *
  14. *
  15. *
  16. * Based on the Portable PHP password hashing framework.
  17. * @see http://www.openwall.com/phpass/
  18. *
  19. * An alternative or custom version of this password hashing API may be
  20. * used by setting the variable password_inc to the name of the PHP file
  21. * containing replacement user_hash_password(), user_check_password(), and
  22. * user_needs_new_hash() functions.
  23. */
  24. /**
  25. * The standard log2 number of iterations for password stretching. This should
  26. * increase by 1 every major FlightPath version in order to counteract increases in the
  27. * speed and power of computers available to crack the hashes.
  28. */
  29. define('FP_HASH_COUNT', 15);
  30. /**
  31. * The minimum allowed log2 number of iterations for password stretching.
  32. */
  33. define('FP_MIN_HASH_COUNT', 7);
  34. /**
  35. * The maximum allowed log2 number of iterations for password stretching.
  36. */
  37. define('FP_MAX_HASH_COUNT', 30);
  38. /**
  39. * The expected (and maximum) number of characters in a hashed password.
  40. */
  41. define('FP_HASH_LENGTH', 55);
  42. /**
  43. * Returns a string for mapping an int to the corresponding base 64 character.
  44. */
  45. function _password_itoa64() {
  46. return './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
  47. }
  48. /**
  49. * Encodes bytes into printable base 64 using the *nix standard from crypt().
  50. *
  51. * @param $input
  52. * The string containing bytes to encode.
  53. * @param $count
  54. * The number of characters (bytes) to encode.
  55. *
  56. * @return
  57. * Encoded string
  58. */
  59. function _password_base64_encode($input, $count) {
  60. $output = '';
  61. $i = 0;
  62. $itoa64 = _password_itoa64();
  63. do {
  64. $value = ord($input[$i++]);
  65. $output .= $itoa64[$value & 0x3f];
  66. if ($i < $count) {
  67. $value |= ord($input[$i]) << 8;
  68. }
  69. $output .= $itoa64[($value >> 6) & 0x3f];
  70. if ($i++ >= $count) {
  71. break;
  72. }
  73. if ($i < $count) {
  74. $value |= ord($input[$i]) << 16;
  75. }
  76. $output .= $itoa64[($value >> 12) & 0x3f];
  77. if ($i++ >= $count) {
  78. break;
  79. }
  80. $output .= $itoa64[($value >> 18) & 0x3f];
  81. } while ($i < $count);
  82. return $output;
  83. }
  84. /**
  85. * Generates a random base 64-encoded salt prefixed with settings for the hash.
  86. *
  87. * Proper use of salts may defeat a number of attacks, including:
  88. * - The ability to try candidate passwords against multiple hashes at once.
  89. * - The ability to use pre-hashed lists of candidate passwords.
  90. * - The ability to determine whether two users have the same (or different)
  91. * password without actually having to guess one of the passwords.
  92. *
  93. * @param $count_log2
  94. * Integer that determines the number of iterations used in the hashing
  95. * process. A larger value is more secure, but takes more time to complete.
  96. *
  97. * @return
  98. * A 12 character string containing the iteration count and a random salt.
  99. */
  100. function _password_generate_salt($count_log2) {
  101. $output = '$S$';
  102. // Ensure that $count_log2 is within set bounds.
  103. $count_log2 = _password_enforce_log2_boundaries($count_log2);
  104. // We encode the final log2 iteration count in base 64.
  105. $itoa64 = _password_itoa64();
  106. $output .= $itoa64[$count_log2];
  107. // 6 bytes is the standard salt for a portable phpass hash.
  108. $output .= _password_base64_encode(fp_random_bytes(6), 6);
  109. //$output .= _password_base64_encode("123456", 6);
  110. return $output;
  111. }
  112. function fp_random_bytes($count) {
  113. // $random_state does not use drupal_static as it stores random bytes.
  114. static $random_state, $bytes, $has_openssl;
  115. $missing_bytes = $count - strlen($bytes);
  116. if ($missing_bytes > 0) {
  117. // PHP versions prior 5.3.4 experienced openssl_random_pseudo_bytes()
  118. // locking on Windows and rendered it unusable.
  119. if (!isset($has_openssl)) {
  120. $has_openssl = version_compare(PHP_VERSION, '5.3.4', '>=') && function_exists('openssl_random_pseudo_bytes');
  121. }
  122. // openssl_random_pseudo_bytes() will find entropy in a system-dependent
  123. // way.
  124. if ($has_openssl) {
  125. $bytes .= openssl_random_pseudo_bytes($missing_bytes);
  126. }
  127. // Else, read directly from /dev/urandom, which is available on many *nix
  128. // systems and is considered cryptographically secure.
  129. elseif ($fh = @fopen('/dev/urandom', 'rb')) {
  130. // PHP only performs buffered reads, so in reality it will always read
  131. // at least 4096 bytes. Thus, it costs nothing extra to read and store
  132. // that much so as to speed any additional invocations.
  133. $bytes .= fread($fh, max(4096, $missing_bytes));
  134. fclose($fh);
  135. }
  136. // If we couldn't get enough entropy, this simple hash-based PRNG will
  137. // generate a good set of pseudo-random bytes on any system.
  138. // Note that it may be important that our $random_state is passed
  139. // through hash() prior to being rolled into $output, that the two hash()
  140. // invocations are different, and that the extra input into the first one -
  141. // the microtime() - is prepended rather than appended. This is to avoid
  142. // directly leaking $random_state via the $output stream, which could
  143. // allow for trivial prediction of further "random" numbers.
  144. if (strlen($bytes) < $count) {
  145. // Initialize on the first call. The contents of $_SERVER includes a mix of
  146. // user-specific and system information that varies a little with each page.
  147. if (!isset($random_state)) {
  148. $random_state = print_r($_SERVER, TRUE);
  149. if (function_exists('getmypid')) {
  150. // Further initialize with the somewhat random PHP process ID.
  151. $random_state .= getmypid();
  152. }
  153. $bytes = '';
  154. }
  155. do {
  156. $random_state = hash('sha256', microtime() . mt_rand() . $random_state);
  157. $bytes .= hash('sha256', mt_rand() . $random_state, TRUE);
  158. }
  159. while (strlen($bytes) < $count);
  160. }
  161. }
  162. $output = substr($bytes, 0, $count);
  163. $bytes = substr($bytes, $count);
  164. return $output;
  165. }
  166. /**
  167. * Ensures that $count_log2 is within set bounds.
  168. *
  169. * @param $count_log2
  170. * Integer that determines the number of iterations used in the hashing
  171. * process. A larger value is more secure, but takes more time to complete.
  172. *
  173. * @return
  174. * Integer within set bounds that is closest to $count_log2.
  175. */
  176. function _password_enforce_log2_boundaries($count_log2) {
  177. if ($count_log2 < FP_MIN_HASH_COUNT) {
  178. return FP_MIN_HASH_COUNT;
  179. }
  180. elseif ($count_log2 > FP_MAX_HASH_COUNT) {
  181. return FP_MAX_HASH_COUNT;
  182. }
  183. return (int) $count_log2;
  184. }
  185. /**
  186. * Hash a password using a secure stretched hash.
  187. *
  188. * By using a salt and repeated hashing the password is "stretched". Its
  189. * security is increased because it becomes much more computationally costly
  190. * for an attacker to try to break the hash by brute-force computation of the
  191. * hashes of a large number of plain-text words or strings to find a match.
  192. *
  193. * @param $algo
  194. * The string name of a hashing algorithm usable by hash(), like 'sha256'.
  195. * @param $password
  196. * The plain-text password to hash.
  197. * @param $setting
  198. * An existing hash or the output of _password_generate_salt(). Must be
  199. * at least 12 characters (the settings and salt).
  200. *
  201. * @return
  202. * A string containing the hashed password (and salt) or FALSE on failure.
  203. * The return string will be truncated at FP_HASH_LENGTH characters max.
  204. */
  205. function _password_crypt($algo, $password, $setting) {
  206. // The first 12 characters of an existing hash are its setting string.
  207. $setting = substr($setting, 0, 12);
  208. if ($setting[0] != '$' || $setting[2] != '$') {
  209. return FALSE;
  210. }
  211. $count_log2 = _password_get_count_log2($setting);
  212. // Hashes may be imported from elsewhere, so we allow != FP_HASH_COUNT
  213. if ($count_log2 < FP_MIN_HASH_COUNT || $count_log2 > FP_MAX_HASH_COUNT) {
  214. return FALSE;
  215. }
  216. $salt = substr($setting, 4, 8);
  217. // Hashes must have an 8 character salt.
  218. if (strlen($salt) != 8) {
  219. return FALSE;
  220. }
  221. // Convert the base 2 logarithm into an integer.
  222. $count = 1 << $count_log2;
  223. // We rely on the hash() function being available in PHP 5.2+.
  224. $hash = hash($algo, $salt . $password, TRUE);
  225. do {
  226. $hash = hash($algo, $hash . $password, TRUE);
  227. } while (--$count);
  228. $len = strlen($hash);
  229. $output = $setting . _password_base64_encode($hash, $len);
  230. // _password_base64_encode() of a 16 byte MD5 will always be 22 characters.
  231. // _password_base64_encode() of a 64 byte sha512 will always be 86 characters.
  232. $expected = 12 + ceil((8 * $len) / 6);
  233. return (strlen($output) == $expected) ? substr($output, 0, FP_HASH_LENGTH) : FALSE;
  234. }
  235. /**
  236. * Parse the log2 iteration count from a stored hash or setting string.
  237. */
  238. function _password_get_count_log2($setting) {
  239. $itoa64 = _password_itoa64();
  240. return strpos($itoa64, $setting[3]);
  241. }
  242. /**
  243. * Hash a password using a secure hash.
  244. *
  245. * @param $password
  246. * A plain-text password.
  247. * @param $count_log2
  248. * Optional integer to specify the iteration count. Generally used only during
  249. * mass operations where a value less than the default is needed for speed.
  250. *
  251. * @return
  252. * A string containing the hashed password (and a salt), or FALSE on failure.
  253. */
  254. function user_hash_password($password, $count_log2 = 0) {
  255. if (empty($count_log2)) {
  256. // Use the standard iteration count.
  257. $count_log2 = variable_get('password_count_log2', FP_HASH_COUNT);
  258. }
  259. return _password_crypt('sha512', $password, _password_generate_salt($count_log2));
  260. }
  261. /**
  262. * Check whether a plain text password matches a stored hashed password.
  263. *
  264. * Alternative implementations of this function may use other data in the
  265. * $account object, for example the uid to look up the hash in a custom table
  266. * or remote database.
  267. *
  268. * @param $password
  269. * A plain-text password
  270. * @param $stored_hash
  271. * The password hash for a user from the database.
  272. *
  273. * @return
  274. * TRUE or FALSE.
  275. */
  276. function user_check_password($password, $stored_hash) {
  277. /*
  278. if (substr($account->pass, 0, 2) == 'U$') {
  279. // This may be an updated password from user_update_7000(). Such hashes
  280. // have 'U' added as the first character and need an extra md5().
  281. $stored_hash = substr($account->pass, 1);
  282. $password = md5($password);
  283. }
  284. else {
  285. $stored_hash = $account->pass;
  286. }
  287. */
  288. $type = substr($stored_hash, 0, 3);
  289. switch ($type) {
  290. case '$S$':
  291. // A normal FlightPath 7 password using sha512.
  292. $hash = _password_crypt('sha512', $password, $stored_hash);
  293. break;
  294. case '$H$':
  295. // phpBB3 uses "$H$" for the same thing as "$P$".
  296. case '$P$':
  297. // A phpass password generated using md5. This is an
  298. // imported password or from an earlier FlightPath version.
  299. $hash = _password_crypt('md5', $password, $stored_hash);
  300. break;
  301. default:
  302. return FALSE;
  303. }
  304. return ($hash && $stored_hash == $hash);
  305. }
  306. /**
  307. * NOTE: This function is from Drupal 7, but at the moment is not being used by FlightPath.
  308. *
  309. *
  310. */
  311. /* Check whether a user's hashed password needs to be replaced with a new hash.
  312. *
  313. * This is typically called during the login process when the plain text
  314. * password is available. A new hash is needed when the desired iteration count
  315. * has changed through a change in the variable password_count_log2 or
  316. * FP_HASH_COUNT or if the user's password hash was generated in an update
  317. * like user_update_7000().
  318. *
  319. * Alternative implementations of this function might use other criteria based
  320. * on the fields in $account.
  321. *
  322. * @param $account
  323. * A user object with at least the fields from the {users} table.
  324. *
  325. * @return
  326. * TRUE or FALSE.
  327. */
  328. function user_needs_new_hash($account) {
  329. // Check whether this was an updated password.
  330. if ((substr($account->password, 0, 3) != '$S$') || (strlen($account->password) != FP_HASH_LENGTH)) {
  331. return TRUE;
  332. }
  333. // Ensure that $count_log2 is within set bounds.
  334. $count_log2 = _password_enforce_log2_boundaries(variable_get('password_count_log2', FP_HASH_COUNT));
  335. // Check whether the iteration count used differs from the standard number.
  336. return (_password_get_count_log2($account->password) !== $count_log2);
  337. }

Functions

Namesort descending Description
fp_random_bytes
user_check_password Check whether a plain text password matches a stored hashed password.
user_hash_password Hash a password using a secure hash.
user_needs_new_hash
_password_base64_encode Encodes bytes into printable base 64 using the *nix standard from crypt().
_password_crypt Hash a password using a secure stretched hash.
_password_enforce_log2_boundaries Ensures that $count_log2 is within set bounds.
_password_generate_salt Generates a random base 64-encoded salt prefixed with settings for the hash.
_password_get_count_log2 Parse the log2 iteration count from a stored hash or setting string.
_password_itoa64 Returns a string for mapping an int to the corresponding base 64 character.

Constants

Namesort descending Description
FP_HASH_COUNT The standard log2 number of iterations for password stretching. This should increase by 1 every major FlightPath version in order to counteract increases in the speed and power of computers available to crack the hashes.
FP_HASH_LENGTH The expected (and maximum) number of characters in a hashed password.
FP_MAX_HASH_COUNT The maximum allowed log2 number of iterations for password stretching.
FP_MIN_HASH_COUNT The minimum allowed log2 number of iterations for password stretching.