1<?php 2 3/** 4 * @file 5 * Secure password hashing functions for user authentication. 6 * 7 * Based on the Portable PHP password hashing framework. 8 * @see http://www.openwall.com/phpass/ 9 * 10 * An alternative or custom version of this password hashing API may be 11 * used by setting the variable password_inc to the name of the PHP file 12 * containing replacement user_hash_password(), user_check_password(), and 13 * user_needs_new_hash() functions. 14 */ 15 16/** 17 * The standard log2 number of iterations for password stretching. This should 18 * increase by 1 every Drupal version in order to counteract increases in the 19 * speed and power of computers available to crack the hashes. 20 */ 21define('DRUPAL_HASH_COUNT', 15); 22 23/** 24 * The minimum allowed log2 number of iterations for password stretching. 25 */ 26define('DRUPAL_MIN_HASH_COUNT', 7); 27 28/** 29 * The maximum allowed log2 number of iterations for password stretching. 30 */ 31define('DRUPAL_MAX_HASH_COUNT', 30); 32 33/** 34 * The expected (and maximum) number of characters in a hashed password. 35 */ 36define('DRUPAL_HASH_LENGTH', 55); 37 38/** 39 * Returns a string for mapping an int to the corresponding base 64 character. 40 */ 41function _password_itoa64() { 42 return './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'; 43} 44 45/** 46 * Encodes bytes into printable base 64 using the *nix standard from crypt(). 47 * 48 * @param $input 49 * The string containing bytes to encode. 50 * @param $count 51 * The number of characters (bytes) to encode. 52 * 53 * @return 54 * Encoded string 55 */ 56function _password_base64_encode($input, $count) { 57 $output = ''; 58 $i = 0; 59 $itoa64 = _password_itoa64(); 60 do { 61 $value = ord($input[$i++]); 62 $output .= $itoa64[$value & 0x3f]; 63 if ($i < $count) { 64 $value |= ord($input[$i]) << 8; 65 } 66 $output .= $itoa64[($value >> 6) & 0x3f]; 67 if ($i++ >= $count) { 68 break; 69 } 70 if ($i < $count) { 71 $value |= ord($input[$i]) << 16; 72 } 73 $output .= $itoa64[($value >> 12) & 0x3f]; 74 if ($i++ >= $count) { 75 break; 76 } 77 $output .= $itoa64[($value >> 18) & 0x3f]; 78 } while ($i < $count); 79 80 return $output; 81} 82 83/** 84 * Generates a random base 64-encoded salt prefixed with settings for the hash. 85 * 86 * Proper use of salts may defeat a number of attacks, including: 87 * - The ability to try candidate passwords against multiple hashes at once. 88 * - The ability to use pre-hashed lists of candidate passwords. 89 * - The ability to determine whether two users have the same (or different) 90 * password without actually having to guess one of the passwords. 91 * 92 * @param $count_log2 93 * Integer that determines the number of iterations used in the hashing 94 * process. A larger value is more secure, but takes more time to complete. 95 * 96 * @return 97 * A 12 character string containing the iteration count and a random salt. 98 */ 99function _password_generate_salt($count_log2) { 100 $output = '$S$'; 101 // Ensure that $count_log2 is within set bounds. 102 $count_log2 = _password_enforce_log2_boundaries($count_log2); 103 // We encode the final log2 iteration count in base 64. 104 $itoa64 = _password_itoa64(); 105 $output .= $itoa64[$count_log2]; 106 // 6 bytes is the standard salt for a portable phpass hash. 107 $output .= _password_base64_encode(drupal_random_bytes(6), 6); 108 return $output; 109} 110 111/** 112 * Ensures that $count_log2 is within set bounds. 113 * 114 * @param $count_log2 115 * Integer that determines the number of iterations used in the hashing 116 * process. A larger value is more secure, but takes more time to complete. 117 * 118 * @return 119 * Integer within set bounds that is closest to $count_log2. 120 */ 121function _password_enforce_log2_boundaries($count_log2) { 122 if ($count_log2 < DRUPAL_MIN_HASH_COUNT) { 123 return DRUPAL_MIN_HASH_COUNT; 124 } 125 elseif ($count_log2 > DRUPAL_MAX_HASH_COUNT) { 126 return DRUPAL_MAX_HASH_COUNT; 127 } 128 129 return (int) $count_log2; 130} 131 132/** 133 * Hash a password using a secure stretched hash. 134 * 135 * By using a salt and repeated hashing the password is "stretched". Its 136 * security is increased because it becomes much more computationally costly 137 * for an attacker to try to break the hash by brute-force computation of the 138 * hashes of a large number of plain-text words or strings to find a match. 139 * 140 * @param $algo 141 * The string name of a hashing algorithm usable by hash(), like 'sha256'. 142 * @param $password 143 * Plain-text password up to 512 bytes (128 to 512 UTF-8 characters) to hash. 144 * @param $setting 145 * An existing hash or the output of _password_generate_salt(). Must be 146 * at least 12 characters (the settings and salt). 147 * 148 * @return 149 * A string containing the hashed password (and salt) or FALSE on failure. 150 * The return string will be truncated at DRUPAL_HASH_LENGTH characters max. 151 */ 152function _password_crypt($algo, $password, $setting) { 153 // Prevent DoS attacks by refusing to hash large passwords. 154 if (strlen($password) > 512) { 155 return FALSE; 156 } 157 // The first 12 characters of an existing hash are its setting string. 158 $setting = substr($setting, 0, 12); 159 160 if ($setting[0] != '$' || $setting[2] != '$') { 161 return FALSE; 162 } 163 $count_log2 = _password_get_count_log2($setting); 164 // Hashes may be imported from elsewhere, so we allow != DRUPAL_HASH_COUNT 165 if ($count_log2 < DRUPAL_MIN_HASH_COUNT || $count_log2 > DRUPAL_MAX_HASH_COUNT) { 166 return FALSE; 167 } 168 $salt = substr($setting, 4, 8); 169 // Hashes must have an 8 character salt. 170 if (strlen($salt) != 8) { 171 return FALSE; 172 } 173 174 // Convert the base 2 logarithm into an integer. 175 $count = 1 << $count_log2; 176 177 // We rely on the hash() function being available in PHP 5.2+. 178 $hash = hash($algo, $salt . $password, TRUE); 179 do { 180 $hash = hash($algo, $hash . $password, TRUE); 181 } while (--$count); 182 183 $len = strlen($hash); 184 $output = $setting . _password_base64_encode($hash, $len); 185 // _password_base64_encode() of a 16 byte MD5 will always be 22 characters. 186 // _password_base64_encode() of a 64 byte sha512 will always be 86 characters. 187 $expected = 12 + ceil((8 * $len) / 6); 188 return (strlen($output) == $expected) ? substr($output, 0, DRUPAL_HASH_LENGTH) : FALSE; 189} 190 191/** 192 * Parse the log2 iteration count from a stored hash or setting string. 193 */ 194function _password_get_count_log2($setting) { 195 $itoa64 = _password_itoa64(); 196 return strpos($itoa64, $setting[3]); 197} 198 199/** 200 * Hash a password using a secure hash. 201 * 202 * @param $password 203 * A plain-text password. 204 * @param $count_log2 205 * Optional integer to specify the iteration count. Generally used only during 206 * mass operations where a value less than the default is needed for speed. 207 * 208 * @return 209 * A string containing the hashed password (and a salt), or FALSE on failure. 210 */ 211function user_hash_password($password, $count_log2 = 0) { 212 if (empty($count_log2)) { 213 // Use the standard iteration count. 214 $count_log2 = variable_get('password_count_log2', DRUPAL_HASH_COUNT); 215 } 216 return _password_crypt('sha512', $password, _password_generate_salt($count_log2)); 217} 218 219/** 220 * Check whether a plain text password matches a stored hashed password. 221 * 222 * Alternative implementations of this function may use other data in the 223 * $account object, for example the uid to look up the hash in a custom table 224 * or remote database. 225 * 226 * @param $password 227 * A plain-text password 228 * @param $account 229 * A user object with at least the fields from the {users} table. 230 * 231 * @return 232 * TRUE or FALSE. 233 */ 234function user_check_password($password, $account) { 235 if (substr($account->pass, 0, 2) == 'U$') { 236 // This may be an updated password from user_update_7000(). Such hashes 237 // have 'U' added as the first character and need an extra md5(). 238 $stored_hash = substr($account->pass, 1); 239 $password = md5($password); 240 } 241 else { 242 $stored_hash = $account->pass; 243 } 244 245 $type = substr($stored_hash, 0, 3); 246 switch ($type) { 247 case '$S$': 248 // A normal Drupal 7 password using sha512. 249 $hash = _password_crypt('sha512', $password, $stored_hash); 250 break; 251 case '$H$': 252 // phpBB3 uses "$H$" for the same thing as "$P$". 253 case '$P$': 254 // A phpass password generated using md5. This is an 255 // imported password or from an earlier Drupal version. 256 $hash = _password_crypt('md5', $password, $stored_hash); 257 break; 258 default: 259 return FALSE; 260 } 261 return ($hash && $stored_hash == $hash); 262} 263 264/** 265 * Check whether a user's hashed password needs to be replaced with a new hash. 266 * 267 * This is typically called during the login process when the plain text 268 * password is available. A new hash is needed when the desired iteration count 269 * has changed through a change in the variable password_count_log2 or 270 * DRUPAL_HASH_COUNT or if the user's password hash was generated in an update 271 * like user_update_7000(). 272 * 273 * Alternative implementations of this function might use other criteria based 274 * on the fields in $account. 275 * 276 * @param $account 277 * A user object with at least the fields from the {users} table. 278 * 279 * @return 280 * TRUE or FALSE. 281 */ 282function user_needs_new_hash($account) { 283 // Check whether this was an updated password. 284 if ((substr($account->pass, 0, 3) != '$S$') || (strlen($account->pass) != DRUPAL_HASH_LENGTH)) { 285 return TRUE; 286 } 287 // Ensure that $count_log2 is within set bounds. 288 $count_log2 = _password_enforce_log2_boundaries(variable_get('password_count_log2', DRUPAL_HASH_COUNT)); 289 // Check whether the iteration count used differs from the standard number. 290 return (_password_get_count_log2($account->pass) !== $count_log2); 291} 292