Last active
April 22, 2020 15:14
-
-
Save slifin/393c375342c9f2fa3207f8f2f269e153 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
class DrupalPassword | |
{ | |
/** | |
* Returns a string for mapping an int to the corresponding base 64 character. | |
* | |
* @return String map for base64. | |
*/ | |
public static function itoa64() : string | |
{ | |
return './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'; | |
} | |
/** | |
* Encodes bytes into printable base 64 using the *nix standard from crypt(). | |
* | |
* @param $input The string containing bytes to encode. | |
* @param $count The number of characters (bytes) to encode. | |
* | |
* @return Encoded string. | |
*/ | |
public static function base64Encode(string $input, int $count) : string | |
{ | |
$output = ''; | |
$i = 0; | |
$itoa64 = self::itoa64(); | |
do { | |
$value = ord($input[$i++]); | |
$output .= $itoa64[$value & 0x3f]; | |
if ($i < $count) { | |
$value |= ord($input[$i]) << 8; | |
} | |
$output .= $itoa64[($value >> 6) & 0x3f]; | |
if ($i++ >= $count) { | |
break; | |
} | |
if ($i < $count) { | |
$value |= ord($input[$i]) << 16; | |
} | |
$output .= $itoa64[($value >> 12) & 0x3f]; | |
if ($i++ >= $count) { | |
break; | |
} | |
$output .= $itoa64[($value >> 18) & 0x3f]; | |
} while ($i < $count); | |
return $output; | |
} | |
/** | |
* Hash a password using a secure stretched hash. | |
* | |
* @param $password Plain-text password up to 512 bytes to hash. | |
* @param $setting An existing hash. | |
* | |
* @return Whether the password matches the stored hash. | |
*/ | |
public static function hashCompare(string $password, string $setting) : bool | |
{ | |
// Prevent DoS attacks by refusing to hash large passwords. | |
if (strlen($password) > 512) { | |
return false; | |
} | |
$storedHash = $setting; | |
// The first 12 characters of an existing hash are its setting string. | |
$setting = substr($setting, 0, 12); | |
if ($setting[0] != '$' || $setting[2] != '$') { | |
return false; | |
} | |
$countLog2 = strpos( | |
self::itoa64(), | |
$setting[3] | |
); | |
// Hashes may be imported from elsewhere, so we allow != DRUPAL_HASH_COUNT | |
if ($countLog2 < 7 || $countLog2 > 30) { | |
return false; | |
} | |
$salt = substr($setting, 4, 8); | |
// Hashes must have an 8 character salt. | |
if (strlen($salt) != 8) { | |
return false; | |
} | |
// Convert the base 2 logarithm into an integer. | |
$count = 1 << $countLog2; | |
// We rely on the hash() function being available in PHP 5.2+. | |
$hash = hash('sha512', $salt . $password, true); | |
do { | |
$hash = hash('sha512', $hash . $password, true); | |
} while (--$count); | |
$len = strlen($hash); | |
$output = $setting . self::base64Encode($hash, $len); | |
// _password_base64_encode() of a 64 byte sha512 | |
// will always be 86 characters. | |
$expected = 12 + ceil((8 * $len) / 6); | |
$calculatedHash | |
= (strlen($output) == $expected) | |
? substr($output, 0, 55) | |
: false; | |
return $calculatedHash === $storedHash; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment