password.inc

  1. 7.x includes/password.inc
  2. 6.x includes/password.inc
  3. 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 string $input
  52. * - The string containing bytes to encode.
  53. * @param int $count
  54. * - The number of characters (bytes) to encode.
  55. *
  56. * @return string
  57. *
  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 $int 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 string
  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. if (function_exists('random_bytes')) {
  114. try {
  115. return random_bytes($count);
  116. } catch (Exception $e) {
  117. // An appropriate source of randomness could not be found. Fall back to a
  118. // less secure implementation.
  119. }
  120. }
  121. static $random_state, $bytes, $has_openssl;
  122. $missing_bytes = $count - strlen((string) $bytes);
  123. if ($missing_bytes > 0) {
  124. // PHP versions prior 5.3.4 experienced openssl_random_pseudo_bytes()
  125. // locking on Windows and rendered it unusable.
  126. if (!isset($has_openssl)) {
  127. $has_openssl = version_compare(PHP_VERSION, '5.3.4', '>=') && function_exists('openssl_random_pseudo_bytes');
  128. }
  129. // openssl_random_pseudo_bytes() will find entropy in a system-dependent
  130. // way.
  131. if ($has_openssl) {
  132. $bytes .= openssl_random_pseudo_bytes($missing_bytes);
  133. }
  134. // Else, read directly from /dev/urandom, which is available on many *nix
  135. // systems and is considered cryptographically secure.
  136. elseif ($fh = @fopen('/dev/urandom', 'rb')) {
  137. // PHP only performs buffered reads, so in reality it will always read
  138. // at least 4096 bytes. Thus, it costs nothing extra to read and store
  139. // that much so as to speed any additional invocations.
  140. $bytes .= fread($fh, max(4096, $missing_bytes));
  141. fclose($fh);
  142. }
  143. // If we couldn't get enough entropy, this simple hash-based PRNG will
  144. // generate a good set of pseudo-random bytes on any system.
  145. // Note that it may be important that our $random_state is passed
  146. // through hash() prior to being rolled into $output, that the two hash()
  147. // invocations are different, and that the extra input into the first one -
  148. // the microtime() - is prepended rather than appended. This is to avoid
  149. // directly leaking $random_state via the $output stream, which could
  150. // allow for trivial prediction of further "random" numbers.
  151. if (strlen($bytes) < $count) {
  152. // Initialize on the first call. The contents of $_SERVER includes a mix of
  153. // user-specific and system information that varies a little with each page.
  154. if (!isset($random_state)) {
  155. $random_state = print_r($_SERVER, TRUE);
  156. if (function_exists('getmypid')) {
  157. // Further initialize with the somewhat random PHP process ID.
  158. $random_state .= getmypid();
  159. }
  160. $bytes = '';
  161. }
  162. do {
  163. $random_state = hash('sha256', microtime() . mt_rand() . $random_state);
  164. $bytes .= hash('sha256', mt_rand() . $random_state, TRUE);
  165. }
  166. while (strlen($bytes) < $count);
  167. }
  168. }
  169. $output = substr($bytes, 0, $count);
  170. $bytes = substr($bytes, $count);
  171. return $output;
  172. }
  173. /**
  174. * Ensures that $count_log2 is within set bounds.
  175. *
  176. * @param int $count_log2
  177. * Integer that determines the number of iterations used in the hashing
  178. * process. A larger value is more secure, but takes more time to complete.
  179. *
  180. * @return int
  181. * Integer within set bounds that is closest to $count_log2.
  182. */
  183. function _password_enforce_log2_boundaries($count_log2) {
  184. if ($count_log2 < FP_MIN_HASH_COUNT) {
  185. return FP_MIN_HASH_COUNT;
  186. }
  187. elseif ($count_log2 > FP_MAX_HASH_COUNT) {
  188. return FP_MAX_HASH_COUNT;
  189. }
  190. return (int) $count_log2;
  191. }
  192. /**
  193. * Hash a password using a secure stretched hash.
  194. *
  195. * By using a salt and repeated hashing the password is "stretched". Its
  196. * security is increased because it becomes much more computationally costly
  197. * for an attacker to try to break the hash by brute-force computation of the
  198. * hashes of a large number of plain-text words or strings to find a match.
  199. *
  200. * @param string $algo
  201. * The string name of a hashing algorithm usable by hash(), like 'sha256'.
  202. * @param string $password
  203. * The plain-text password to hash.
  204. * @param string $setting
  205. * An existing hash or the output of _password_generate_salt(). Must be
  206. * at least 12 characters (the settings and salt).
  207. *
  208. * @return string|false
  209. * A string containing the hashed password (and salt) or FALSE on failure.
  210. * The return string will be truncated at FP_HASH_LENGTH characters max.
  211. */
  212. function _password_crypt($algo, $password, $setting) {
  213. // The first 12 characters of an existing hash are its setting string.
  214. $setting = substr($setting, 0, 12);
  215. if ($setting[0] != '$' || $setting[2] != '$') {
  216. return FALSE;
  217. }
  218. $count_log2 = _password_get_count_log2($setting);
  219. // Hashes may be imported from elsewhere, so we allow != FP_HASH_COUNT
  220. if ($count_log2 < FP_MIN_HASH_COUNT || $count_log2 > FP_MAX_HASH_COUNT) {
  221. return FALSE;
  222. }
  223. $salt = substr($setting, 4, 8);
  224. // Hashes must have an 8 character salt.
  225. if (strlen($salt) != 8) {
  226. return FALSE;
  227. }
  228. // Convert the base 2 logarithm into an integer.
  229. $count = 1 << $count_log2;
  230. // We rely on the hash() function being available in PHP 5.2+.
  231. $hash = hash($algo, $salt . $password, TRUE);
  232. do {
  233. $hash = hash($algo, $hash . $password, TRUE);
  234. } while (--$count);
  235. $len = strlen($hash);
  236. $output = $setting . _password_base64_encode($hash, $len);
  237. // _password_base64_encode() of a 16 byte MD5 will always be 22 characters.
  238. // _password_base64_encode() of a 64 byte sha512 will always be 86 characters.
  239. $expected = 12 + ceil((8 * $len) / 6);
  240. return (strlen($output) == $expected) ? substr($output, 0, FP_HASH_LENGTH) : FALSE;
  241. }
  242. /**
  243. * Parse the log2 iteration count from a stored hash or setting string.
  244. */
  245. function _password_get_count_log2($setting) {
  246. $itoa64 = _password_itoa64();
  247. return strpos($itoa64, $setting[3]);
  248. }
  249. /**
  250. * Hash a password using a secure hash.
  251. *
  252. * @param string $password
  253. * A plain-text password.
  254. * @param int $count_log2
  255. * Optional integer to specify the iteration count. Generally used only during
  256. * mass operations where a value less than the default is needed for speed.
  257. *
  258. * @return string
  259. * A string containing the hashed password (and a salt), or FALSE on failure.
  260. */
  261. function user_hash_password($password, $count_log2 = 0) {
  262. if (empty($count_log2)) {
  263. // Use the standard iteration count.
  264. $count_log2 = variable_get('password_count_log2', FP_HASH_COUNT);
  265. }
  266. return _password_crypt('sha512', $password, _password_generate_salt($count_log2));
  267. }
  268. /**
  269. * Check whether a plain text password matches a stored hashed password.
  270. *
  271. * Alternative implementations of this function may use other data in the
  272. * $account object, for example the uid to look up the hash in a custom table
  273. * or remote database.
  274. *
  275. * @param string $password
  276. * A plain-text password
  277. * @param string $stored_hash
  278. * The password hash for a user from the database.
  279. *
  280. * @return bool
  281. * TRUE or FALSE.
  282. */
  283. function user_check_password($password, $stored_hash) {
  284. if (!$password) return FALSE;
  285. if (!$stored_hash) return FALSE;
  286. $type = substr($stored_hash, 0, 3);
  287. switch ($type) {
  288. case '$S$':
  289. // A normal FlightPath password using sha512.
  290. $hash = _password_crypt('sha512', $password, $stored_hash);
  291. break;
  292. case '$H$':
  293. // phpBB3 uses "$H$" for the same thing as "$P$".
  294. case '$P$':
  295. // A phpass password generated using md5. This is an
  296. // imported password or from an earlier FlightPath version.
  297. $hash = _password_crypt('md5', $password, $stored_hash);
  298. break;
  299. default:
  300. return FALSE;
  301. }
  302. return ($hash && $stored_hash == $hash);
  303. }
  304. /**
  305. * This function checks a plain text password to make sure it meets our minimum complexity requirements.
  306. *
  307. * Complexity requirement is based on NIST minimum requirements, which stresses length over arbitrarily
  308. * complicated rules. This is why our complexity rules are faily basic.
  309. *
  310. * Returns TRUE if password satisfies complexity.
  311. *
  312. * Returns FALSE if it does not.
  313. *
  314. */
  315. function password_validate_complexity($plain_text_password) {
  316. // At least 1 text character
  317. // At least 1 non-character number
  318. // At least 12 characters in length
  319. // No digit or text character can appear more than twice in a row.
  320. $pattern = '/^(?=.*\D)(?=.*\d)(?!.*(.)\1\1).{12,}$/';
  321. return preg_match($pattern, $plain_text_password);
  322. }
  323. /**
  324. * Returns a list of complexity rules as either HTML (default) or an array.
  325. * @param boolean $bool_return_array
  326. */
  327. function password_get_complexity_rules($bool_return_array = FALSE) {
  328. $html = "";
  329. $arr = array();
  330. $arr[] = t("Must be at least 12 characters long");
  331. $arr[] = t("Must contain at least one number");
  332. $arr[] = t("Must contain at least one letter or symbol");
  333. $arr[] = t("Cannot contain the same character more than twice in a row");
  334. $html .= "<ul class='password-complexity-rules'>";
  335. foreach ($arr as $r) {
  336. $html .= "<li>$r</li>";
  337. }
  338. $html .= "</ul>";
  339. if ($bool_return_array) return $arr;
  340. return $html;
  341. }
  342. /**
  343. * NOTE: This function is from Drupal 7, but at the moment is not being used by FlightPath.
  344. *
  345. *
  346. */
  347. /* Check whether a user's hashed password needs to be replaced with a new hash.
  348. *
  349. * This is typically called during the login process when the plain text
  350. * password is available. A new hash is needed when the desired iteration count
  351. * has changed through a change in the variable password_count_log2 or
  352. * FP_HASH_COUNT or if the user's password hash was generated in an update
  353. * like user_update_7000().
  354. *
  355. * Alternative implementations of this function might use other criteria based
  356. * on the fields in $account.
  357. *
  358. * @param $account
  359. * A user object with at least the fields from the {users} table.
  360. *
  361. * @return
  362. * TRUE or FALSE.
  363. */
  364. function user_needs_new_hash($account) {
  365. // Check whether this was an updated password.
  366. if ((substr($account->password, 0, 3) != '$S$') || (strlen($account->password) != FP_HASH_LENGTH)) {
  367. return TRUE;
  368. }
  369. // Ensure that $count_log2 is within set bounds.
  370. $count_log2 = _password_enforce_log2_boundaries(variable_get('password_count_log2', FP_HASH_COUNT));
  371. // Check whether the iteration count used differs from the standard number.
  372. return (_password_get_count_log2($account->password) !== $count_log2);
  373. }

Functions

Namesort descending Description
fp_random_bytes
password_get_complexity_rules Returns a list of complexity rules as either HTML (default) or an array.
password_validate_complexity This function checks a plain text password to make sure it meets our minimum complexity requirements.
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.