/[drupal]/drupal/includes/password.inc
ViewVC logotype

Contents of /drupal/includes/password.inc

Parent Directory Parent Directory | Revision Log Revision Log | View Revision Graph Revision Graph


Revision 1.6 - (show annotations) (download) (as text)
Thu Feb 26 07:30:26 2009 UTC (9 months ago) by webchick
Branch: MAIN
CVS Tags: DRUPAL-7-0-UNSTABLE-10, DRUPAL-7-0-UNSTABLE-8, DRUPAL-7-0-UNSTABLE-9, DRUPAL-7-0-UNSTABLE-6, DRUPAL-7-0-UNSTABLE-7, HEAD
Changes since 1.5: +3 -3 lines
File MIME type: text/x-php
Roll-back of users -> user table name change in #330983: Broken pgsql is no fun.
1 <?php
2 // $Id: password.inc,v 1.5 2009/02/18 15:19:55 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

  ViewVC Help
Powered by ViewVC 1.1.2