Spike PHPCoverage Details: password.inc

Line #FrequencySource Line
1 <?php
2 // $Id: password.inc,v 1.1 2008/04/01 06:25:31 dries Exp $
3 
4 /**
5  * @file
6  * Secure password hashing functions for user authentication.
7  *
8  * Based on the Portable PHP password hashing framework.
9  * @see http://www.openwall.com/phpass/
10  *
11  * An alternative or custom version of this password hashing API may be
12  * used by setting the variable password_inc to the name of the PHP file
13  * containing replacement user_hash_password(), user_check_password(), and
14  * user_needs_new_hash() functions.
15  */
16 
17 /**
18  * The standard log2 number of iterations for password stretching. This should
19  * increase by 1 at least every other Drupal version in order to counteract
20  * increases in the speed and power of computers available to crack the hashes.
21  */
221define('DRUPAL_HASH_COUNT', 14);
23 
24 /**
25  * The min and max allowed log2 number of iterations for password stretching.
26  */
271define('DRUPAL_MIN_HASH_COUNT', 7);
281define('DRUPAL_MAX_HASH_COUNT', 30);
29 
30 /**
31  * Returns a string for mapping an int to the corresponding base 64 character.
32  */
33 function _password_itoa64() {
341  return './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
35 }
36 
37 /**
38  * Encode bytes into printable base 64 using the *nix standard from crypt().
39  *
40  * @param $input
41  *   The string containing bytes to encode.
42  * @param $count
43  *   The number of characters (bytes) to encode.
44  *
45  * @return
46  *   Encoded string
47  */
48 function  _password_base64_encode($input, $count)  {
491  $output = '';
501  $i = 0;
511  $itoa64 = _password_itoa64();
52   do {
531    $value = ord($input[$i++]);
541    $output .= $itoa64[$value & 0x3f];
551    if ($i < $count) {
561      $value |= ord($input[$i]) << 8;
57     }
581    $output .= $itoa64[($value >> 6) & 0x3f];
591    if ($i++ >= $count) {
601      break;
61     }
621    if ($i < $count) {
631      $value |= ord($input[$i]) << 16;
64     }
651    $output .= $itoa64[($value >> 12) & 0x3f];
661    if ($i++ >= $count) {
67       break;
68     }
691    $output .= $itoa64[($value >> 18) & 0x3f];
701  } while ($i < $count);
71 
721  return $output;
73 }
74 
75 /**
76  * Generates a random base 64-encoded salt prefixed with settings for the hash.
77  *
78  * Proper use of salts may defeat a number of attacks, including:
79  *  - The ability to try candidate passwords against multiple hashes at once.
80  *  - The ability to use pre-hashed lists of candidate passwords.
81  *  - The ability to determine whether two users have the same (or different)
82  *    password without actually having to guess one of the passwords.
83  *
84  * @param $count_log2
85  *   Integer that determines the number of iterations used in the hashing
86  *   process. A larger value is more secure, but takes more time to complete.
87  *
88  * @return
89  *   A 12 character string containing the iteration count and a random salt.
90  */
91 function _password_generate_salt($count_log2) {
921  $output = '$P$';
93   // Minimum log2 iterations is DRUPAL_MIN_HASH_COUNT.
941  $count_log2 = max($count_log2, DRUPAL_MIN_HASH_COUNT);
95   // Maximum log2 iterations is DRUPAL_MAX_HASH_COUNT.
96   // We encode the final log2 iteration count in base 64.
971  $itoa64 = _password_itoa64();
981  $output .= $itoa64[min($count_log2, DRUPAL_MAX_HASH_COUNT)];
99   // 6 bytes is the standard salt for a portable phpass hash.
1001  $output .= _password_base64_encode(drupal_random_bytes(6), 6);
1011  return $output;
102 }
103 
104 /**
105  * Hash a password using a secure stretched hash.
106  *
107  * By using a salt and repeated hashing the password is "stretched". Its
108  * security is increased because it becomes much more computationally costly
109  * for an attacker to try to break the hash by brute-force computation of the
110  * hashes of a large number of plain-text words or strings to find a match.
111  *
112  * @param $password
113  *   The plain-text password to hash.
114  * @param $setting
115  *   An existing hash or the output of _password_generate_salt().
116  *
117  * @return
118  *   A string containing the hashed password (and salt) or FALSE on failure.
119  */
120 function _password_crypt($password, $setting)  {
121   // The first 12 characters of an existing hash are its setting string.
1221  $setting = substr($setting, 0, 12);
123 
1241  if (substr($setting, 0, 3) != '$P$') {
125     return FALSE;
126   }
1271  $count_log2 = _password_get_count_log2($setting);
128   // Hashes may be imported from elsewhere, so we allow != DRUPAL_HASH_COUNT
1291  if ($count_log2 < DRUPAL_MIN_HASH_COUNT || $count_log2 > DRUPAL_MAX_HASH_COUNT) {
130     return FALSE;
131   }
1321  $salt = substr($setting, 4, 8);
133   // Hashes must have an 8 character salt.
1341  if (strlen($salt) != 8) {
135     return FALSE;
136   }
137 
138   // We must use md5() or sha1() here since they are the only cryptographic
139   // primitives always available in PHP 5. To implement our own low-level
140   // cryptographic function in PHP would result in much worse performance and
141   // consequently in lower iteration counts and hashes that are quicker to crack
142   // (by non-PHP code).
143 
1441  $count = 1 << $count_log2;
145 
1461  $hash = md5($salt . $password, TRUE);
147   do {
1481    $hash = md5($hash . $password, TRUE);
149   } while (--$count);
150 
1511  $output =  $setting . _password_base64_encode($hash, 16);
152   // _password_base64_encode() of a 16 byte MD5 will always be 22 characters.
1531  return (strlen($output) == 34) ? $output : FALSE;
154 }
155 
156 /**
157  * Parse the log2 iteration count from a stored hash or setting string.
158  */
159 function _password_get_count_log2($setting) {
1601  $itoa64 = _password_itoa64();
1611  return strpos($itoa64, $setting[3]);
162 }
163 
164 /**
165  * Hash a password using a secure hash.
166  *
167  * @param $password
168  *   A plain-text password.
169  * @param $count_log2
170  *   Optional integer to specify the iteration count. Generally used only during
171  *   mass operations where a value less than the default is needed for speed.
172  *
173  * @return
174  *   A string containing the hashed password (and a salt), or FALSE on failure.
175  */
176 function user_hash_password($password, $count_log2 = 0) {
1771  if (empty($count_log2)) {
178     // Use the standard iteration count.
1791    $count_log2 = variable_get('password_count_log2', DRUPAL_HASH_COUNT);
180   }
1811  return _password_crypt($password, _password_generate_salt($count_log2));
182 }
183 
184 /**
185  * Check whether a plain text password matches a stored hashed password.
186  *
187  * Alternative implementations of this function may use other data in the
188  * $account object, for example the uid to look up the hash in a custom table
189  * or remote database.
190  *
191  * @param $password
192  *   A plain-text password
193  * @param $account
194  *   A user object with at least the fields from the {users} table.
195  *
196  * @return
197  *   TRUE or FALSE.
198  */
199 function user_check_password($password, $account) {
200   if (substr($account->pass, 0, 3) == 'U$P') {
201     // This may be an updated password from user_update_7000(). Such hashes
202     // have 'U' added as the first character and need an extra md5().
203     $stored_hash = substr($account->pass, 1);
204     $password = md5($password);
205   }
206   else {
207     $stored_hash = $account->pass;
208   }
209   $hash = _password_crypt($password, $stored_hash);
210   return ($hash && $stored_hash == $hash);
211 }
212 
213 /**
214  * Check whether a user's hashed password needs to be replaced with a new hash.
215  *
216  * This is typically called during the login process when the plain text
217  * password is available.  A new hash is needed when the desired iteration count
218  * has changed through a change in the variable password_count_log2 or
219  * DRUPAL_HASH_COUNT or if the user's password hash was generated in an update
220  * like user_update_7000().
221  *
222  * Alternative implementations of this function might use other criteria based
223  * on the fields in $account.
224  *
225  * @param $account
226  *   A user object with at least the fields from the {users} table.
227  *
228  * @return
229  *   TRUE or FALSE.
230  */
231 function user_needs_new_hash($account) {
232   // Check whether this was an updated password.
233   if ((substr($account->pass, 0, 3) != '$P$') || (strlen($account->pass) != 34)) {
234     return TRUE;
235   }
236   // Check whether the iteration count used differs from the standard number.
237   return (_password_get_count_log2($account->pass) != variable_get('password_count_log2', DRUPAL_HASH_COUNT));
238 }
239