Code coverage for /20081101/includes/password.inc

Line #Times calledCode
1
<?php
2
// $Id: password.inc,v 1.3 2008/05/26 17:12:54 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
 */
22267
define('DRUPAL_HASH_COUNT', 14);
23
24
/**
25
 * The minimum allowed log2 number of iterations for password stretching.
26
 */
27267
define('DRUPAL_MIN_HASH_COUNT', 7);
28
29
/**
30
 * The maximum allowed log2 number of iterations for password stretching.
31
 */
32267
define('DRUPAL_MAX_HASH_COUNT', 30);
33
34
/**
35
 * Returns a string for mapping an int to the corresponding base 64
character.
36
 */
37267
function _password_itoa64() {
38267
  return
'./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
390
}
40
41
/**
42
 * Encode bytes into printable base 64 using the *nix standard from
crypt().
43
 *
44
 * @param $input
45
 *   The string containing bytes to encode.
46
 * @param $count
47
 *   The number of characters (bytes) to encode.
48
 *
49
 * @return
50
 *   Encoded string
51
 */
52267
function _password_base64_encode($input, $count)  {
53267
  $output = '';
54267
  $i = 0;
55267
  $itoa64 = _password_itoa64();
56
  do {
57267
    $value = ord($input[$i++]);
58267
    $output .= $itoa64[$value & 0x3f];
59267
    if ($i < $count) {
60267
      $value |= ord($input[$i]) << 8;
61267
    }
62267
    $output .= $itoa64[($value >> 6) & 0x3f];
63267
    if ($i++ >= $count) {
64267
      break;
650
    }
66267
    if ($i < $count) {
67267
      $value |= ord($input[$i]) << 16;
68267
    }
69267
    $output .= $itoa64[($value >> 12) & 0x3f];
70267
    if ($i++ >= $count) {
710
      break;
720
    }
73267
    $output .= $itoa64[($value >> 18) & 0x3f];
74267
  } while ($i < $count);
75
76267
  return $output;
770
}
78
79
/**
80
 * Generates a random base 64-encoded salt prefixed with settings for the
hash.
81
 *
82
 * Proper use of salts may defeat a number of attacks, including:
83
 *  - The ability to try candidate passwords against multiple hashes at
once.
84
 *  - The ability to use pre-hashed lists of candidate passwords.
85
 *  - The ability to determine whether two users have the same (or
different)
86
 *    password without actually having to guess one of the passwords.
87
 *
88
 * @param $count_log2
89
 *   Integer that determines the number of iterations used in the hashing
90
 *   process. A larger value is more secure, but takes more time to
complete.
91
 *
92
 * @return
93
 *   A 12 character string containing the iteration count and a random
salt.
94
 */
95267
function _password_generate_salt($count_log2) {
9680
  $output = '$P$';
97
  // Minimum log2 iterations is DRUPAL_MIN_HASH_COUNT.
9880
  $count_log2 = max($count_log2, DRUPAL_MIN_HASH_COUNT);
99
  // Maximum log2 iterations is DRUPAL_MAX_HASH_COUNT.
100
  // We encode the final log2 iteration count in base 64.
10180
  $itoa64 = _password_itoa64();
10280
  $output .= $itoa64[min($count_log2, DRUPAL_MAX_HASH_COUNT)];
103
  // 6 bytes is the standard salt for a portable phpass hash.
10480
  $output .= _password_base64_encode(drupal_random_bytes(6), 6);
10580
  return $output;
1060
}
107
108
/**
109
 * Hash a password using a secure stretched hash.
110
 *
111
 * By using a salt and repeated hashing the password is "stretched". Its
112
 * security is increased because it becomes much more computationally
costly
113
 * for an attacker to try to break the hash by brute-force computation of
the
114
 * hashes of a large number of plain-text words or strings to find a match.
115
 *
116
 * @param $password
117
 *   The plain-text password to hash.
118
 * @param $setting
119
 *   An existing hash or the output of _password_generate_salt().
120
 *
121
 * @return
122
 *   A string containing the hashed password (and salt) or FALSE on
failure.
123
 */
124267
function _password_crypt($password, $setting)  {
125
  // The first 12 characters of an existing hash are its setting string.
126267
  $setting = substr($setting, 0, 12);
127
128267
  if (substr($setting, 0, 3) != '$P$') {
1290
    return FALSE;
1300
  }
131267
  $count_log2 = _password_get_count_log2($setting);
132
  // Hashes may be imported from elsewhere, so we allow !=
DRUPAL_HASH_COUNT
133267
  if ($count_log2 < DRUPAL_MIN_HASH_COUNT || $count_log2 >
DRUPAL_MAX_HASH_COUNT) {
1340
    return FALSE;
1350
  }
136267
  $salt = substr($setting, 4, 8);
137
  // Hashes must have an 8 character salt.
138267
  if (strlen($salt) != 8) {
1390
    return FALSE;
1400
  }
141
142
  // We must use md5() or sha1() here since they are the only cryptographic
143
  // primitives always available in PHP 5. To implement our own low-level
144
  // cryptographic function in PHP would result in much worse performance
and
145
  // consequently in lower iteration counts and hashes that are quicker to
crack
146
  // (by non-PHP code).
147
148267
  $count = 1 << $count_log2;
149
150267
  $hash = md5($salt . $password, TRUE);
151
  do {
152267
    $hash = md5($hash . $password, TRUE);
1530
  } while (--$count);
154
155267
  $output =  $setting . _password_base64_encode($hash, 16);
156
  // _password_base64_encode() of a 16 byte MD5 will always be 22
characters.
157267
  return (strlen($output) == 34) ? $output : FALSE;
1580
}
159
160
/**
161
 * Parse the log2 iteration count from a stored hash or setting string.
162
 */
163267
function _password_get_count_log2($setting) {
164267
  $itoa64 = _password_itoa64();
165267
  return strpos($itoa64, $setting[3]);
1660
}
167
168
/**
169
 * Hash a password using a secure hash.
170
 *
171
 * @param $password
172
 *   A plain-text password.
173
 * @param $count_log2
174
 *   Optional integer to specify the iteration count. Generally used only
during
175
 *   mass operations where a value less than the default is needed for
speed.
176
 *
177
 * @return
178
 *   A string containing the hashed password (and a salt), or FALSE on
failure.
179
 */
180267
function user_hash_password($password, $count_log2 = 0) {
18180
  if (empty($count_log2)) {
182
    // Use the standard iteration count.
18380
    $count_log2 = variable_get('password_count_log2', DRUPAL_HASH_COUNT);
18480
  }
18580
  return _password_crypt($password, _password_generate_salt($count_log2));
1860
}
187
188
/**
189
 * Check whether a plain text password matches a stored hashed password.
190
 *
191
 * Alternative implementations of this function may use other data in the
192
 * $account object, for example the uid to look up the hash in a custom
table
193
 * or remote database.
194
 *
195
 * @param $password
196
 *   A plain-text password
197
 * @param $account
198
 *   A user object with at least the fields from the {users} table.
199
 *
200
 * @return
201
 *   TRUE or FALSE.
202
 */
203267
function user_check_password($password, $account) {
204187
  if (substr($account->pass, 0, 3) == 'U$P') {
205
    // This may be an updated password from user_update_7000(). Such hashes
206
    // have 'U' added as the first character and need an extra md5().
2070
    $stored_hash = substr($account->pass, 1);
2080
    $password = md5($password);
2090
  }
210
  else {
211187
    $stored_hash = $account->pass;
212
  }
213187
  $hash = _password_crypt($password, $stored_hash);
214187
  return ($hash && $stored_hash == $hash);
2150
}
216
217
/**
218
 * Check whether a user's hashed password needs to be replaced with a new
hash.
219
 *
220
 * This is typically called during the login process when the plain text
221
 * password is available.  A new hash is needed when the desired iteration
count
222
 * has changed through a change in the variable password_count_log2 or
223
 * DRUPAL_HASH_COUNT or if the user's password hash was generated in an
update
224
 * like user_update_7000().
225
 *
226
 * Alternative implementations of this function might use other criteria
based
227
 * on the fields in $account.
228
 *
229
 * @param $account
230
 *   A user object with at least the fields from the {users} table.
231
 *
232
 * @return
233
 *   TRUE or FALSE.
234
 */
235267
function user_needs_new_hash($account) {
236
  // Check whether this was an updated password.
237185
  if ((substr($account->pass, 0, 3) != '$P$') || (strlen($account->pass) !=
34)) {
2380
    return TRUE;
2390
  }
240
  // Check whether the iteration count used differs from the standard
number.
241185
  return (_password_get_count_log2($account->pass) !=
variable_get('password_count_log2', DRUPAL_HASH_COUNT));
2420
}
243
244267