Skip to content

Instantly share code, notes, and snippets.

@slifin
Last active April 22, 2020 15:14
Show Gist options
  • Save slifin/393c375342c9f2fa3207f8f2f269e153 to your computer and use it in GitHub Desktop.
Save slifin/393c375342c9f2fa3207f8f2f269e153 to your computer and use it in GitHub Desktop.
<?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