Line # | Frequency | Source 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 | | */ |
22 | 1 | define('DRUPAL_HASH_COUNT', 14); |
23 | |
|
24 | | /** |
25 | | * The min and max allowed log2 number of iterations for password stretching. |
26 | | */ |
27 | 1 | define('DRUPAL_MIN_HASH_COUNT', 7); |
28 | 1 | define('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() { |
34 | 1 | 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) { |
49 | 1 | $output = ''; |
50 | 1 | $i = 0; |
51 | 1 | $itoa64 = _password_itoa64(); |
52 | | do { |
53 | 1 | $value = ord($input[$i++]); |
54 | 1 | $output .= $itoa64[$value & 0x3f]; |
55 | 1 | if ($i < $count) { |
56 | 1 | $value |= ord($input[$i]) << 8; |
57 | | } |
58 | 1 | $output .= $itoa64[($value >> 6) & 0x3f]; |
59 | 1 | if ($i++ >= $count) { |
60 | 1 | break; |
61 | | } |
62 | 1 | if ($i < $count) { |
63 | 1 | $value |= ord($input[$i]) << 16; |
64 | | } |
65 | 1 | $output .= $itoa64[($value >> 12) & 0x3f]; |
66 | 1 | if ($i++ >= $count) { |
67 | | break; |
68 | | } |
69 | 1 | $output .= $itoa64[($value >> 18) & 0x3f]; |
70 | 1 | } while ($i < $count); |
71 | |
|
72 | 1 | 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) { |
92 | 1 | $output = '$P$'; |
93 | | // Minimum log2 iterations is DRUPAL_MIN_HASH_COUNT. |
94 | 1 | $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. |
97 | 1 | $itoa64 = _password_itoa64(); |
98 | 1 | $output .= $itoa64[min($count_log2, DRUPAL_MAX_HASH_COUNT)]; |
99 | | // 6 bytes is the standard salt for a portable phpass hash. |
100 | 1 | $output .= _password_base64_encode(drupal_random_bytes(6), 6); |
101 | 1 | 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. |
122 | 1 | $setting = substr($setting, 0, 12); |
123 | |
|
124 | 1 | if (substr($setting, 0, 3) != '$P$') { |
125 | | return FALSE; |
126 | | } |
127 | 1 | $count_log2 = _password_get_count_log2($setting); |
128 | | // Hashes may be imported from elsewhere, so we allow != DRUPAL_HASH_COUNT |
129 | 1 | if ($count_log2 < DRUPAL_MIN_HASH_COUNT || $count_log2 > DRUPAL_MAX_HASH_COUNT) { |
130 | | return FALSE; |
131 | | } |
132 | 1 | $salt = substr($setting, 4, 8); |
133 | | // Hashes must have an 8 character salt. |
134 | 1 | 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 | |
|
144 | 1 | $count = 1 << $count_log2; |
145 | |
|
146 | 1 | $hash = md5($salt . $password, TRUE); |
147 | | do { |
148 | 1 | $hash = md5($hash . $password, TRUE); |
149 | | } while (--$count); |
150 | |
|
151 | 1 | $output = $setting . _password_base64_encode($hash, 16); |
152 | | // _password_base64_encode() of a 16 byte MD5 will always be 22 characters. |
153 | 1 | 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) { |
160 | 1 | $itoa64 = _password_itoa64(); |
161 | 1 | 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) { |
177 | 1 | if (empty($count_log2)) { |
178 | | // Use the standard iteration count. |
179 | 1 | $count_log2 = variable_get('password_count_log2', DRUPAL_HASH_COUNT); |
180 | | } |
181 | 1 | 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 | |
|