Simpletest Coverage - includes/password.inc

1 <?php
2 // $Id: password.inc,v 1.6 2009/02/26 07:30:26 webchick 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 */
22 define('DRUPAL_HASH_COUNT', 14);
23
24 /**
25 * The minimum allowed log2 number of iterations for password stretching.
26 */
27 define('DRUPAL_MIN_HASH_COUNT', 7);
28
29 /**
30 * The maximum allowed log2 number of iterations for password stretching.
31 */
32 define('DRUPAL_MAX_HASH_COUNT', 30);
33
34 /**
35 * Returns a string for mapping an int to the corresponding base 64 character.
36 */
37 function _password_itoa64() {
38 return './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
39 }
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 */
52 function _password_base64_encode($input, $count) {
53 $output = '';
54 $i = 0;
55 $itoa64 = _password_itoa64();
56 do {
57 $value = ord($input[$i++]);
58 $output .= $itoa64[$value & 0x3f];
59 if ($i < $count) {
60 $value |= ord($input[$i]) << 8;
61 }
62 $output .= $itoa64[($value >> 6) & 0x3f];
63 if ($i++ >= $count) {
64 break;
65 }
66 if ($i < $count) {
67 $value |= ord($input[$i]) << 16;
68 }
69 $output .= $itoa64[($value >> 12) & 0x3f];
70 if ($i++ >= $count) {
71 break;
72 }
73 $output .= $itoa64[($value >> 18) & 0x3f];
74 } while ($i < $count);
75
76 return $output;
77 }
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 */
95 function _password_generate_salt($count_log2) {
96 $output = '$P$';
97 // Minimum log2 iterations is DRUPAL_MIN_HASH_COUNT.
98 $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.
101 $itoa64 = _password_itoa64();
102 $output .= $itoa64[min($count_log2, DRUPAL_MAX_HASH_COUNT)];
103 // 6 bytes is the standard salt for a portable phpass hash.
104 $output .= _password_base64_encode(drupal_random_bytes(6), 6);
105 return $output;
106 }
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 */
124 function _password_crypt($password, $setting) {
125 // The first 12 characters of an existing hash are its setting string.
126 $setting = substr($setting, 0, 12);
127
128 if (substr($setting, 0, 3) != '$P$') {
129 return FALSE;
130 }
131 $count_log2 = _password_get_count_log2($setting);
132 // Hashes may be imported from elsewhere, so we allow != DRUPAL_HASH_COUNT
133 if ($count_log2 < DRUPAL_MIN_HASH_COUNT || $count_log2 > DRUPAL_MAX_HASH_COUNT) {
134 return FALSE;
135 }
136 $salt = substr($setting, 4, 8);
137 // Hashes must have an 8 character salt.
138 if (strlen($salt) != 8) {
139 return FALSE;
140 }
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
148 $count = 1 << $count_log2;
149
150 $hash = md5($salt . $password, TRUE);
151 do {
152 $hash = md5($hash . $password, TRUE);
153 } while (--$count);
154
155 $output = $setting . _password_base64_encode($hash, 16);
156 // _password_base64_encode() of a 16 byte MD5 will always be 22 characters.
157 return (strlen($output) == 34) ? $output : FALSE;
158 }
159
160 /**
161 * Parse the log2 iteration count from a stored hash or setting string.
162 */
163 function _password_get_count_log2($setting) {
164 $itoa64 = _password_itoa64();
165 return strpos($itoa64, $setting[3]);
166 }
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 */
180 function user_hash_password($password, $count_log2 = 0) {
181 if (empty($count_log2)) {
182 // Use the standard iteration count.
183 $count_log2 = variable_get('password_count_log2', DRUPAL_HASH_COUNT);
184 }
185 return _password_crypt($password, _password_generate_salt($count_log2));
186 }
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 */
203 function user_check_password($password, $account) {
204 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().
207 $stored_hash = substr($account->pass, 1);
208 $password = md5($password);
209 }
210 else {
211 $stored_hash = $account->pass;
212 }
213 $hash = _password_crypt($password, $stored_hash);
214 return ($hash && $stored_hash == $hash);
215 }
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 */
235 function user_needs_new_hash($account) {
236 // Check whether this was an updated password.
237 if ((substr($account->pass, 0, 3) != '$P$') || (strlen($account->pass) != 34)) {
238 return TRUE;
239 }
240 // Check whether the iteration count used differs from the standard number.
241 return (_password_get_count_log2($account->pass) != variable_get('password_count_log2', DRUPAL_HASH_COUNT));
242 }
243
244

Legend

Missed
lines code that were not excersized during program execution.
Covered
lines code were excersized during program execution.
Comment/non executable
Comment or non-executable line of code.
Dead
lines of code that according to xdebug could not be executed. This is counted as coverage code because in almost all cases it is code that runnable.