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