| 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 |