Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cleanup PHPASS password implementations #4762

Merged
merged 8 commits into from
Apr 28, 2022
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -16,74 +16,16 @@
*/
final class Drupal8 implements IPasswordAlgorithm
{
private $itoa64 = './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
use TPhpass;

private const COSTS = 15;

/**
* Returns the hashed password, with the given settings.
*/
private function hashDrupal(string $password, string $settings): string
{
$output = '*';

// Check for correct hash
if (\mb_substr($settings, 0, 3, '8bit') !== '$S$') {
return $output;
}

$count_log2 = \mb_strpos($this->itoa64, $settings[3], 0, '8bit');

if ($count_log2 < 7 || $count_log2 > 30) {
return $output;
}

$count = 1 << $count_log2;
$salt = \mb_substr($settings, 4, 8, '8bit');

if (\mb_strlen($salt, '8bit') != 8) {
return $output;
}

$hash = \hash('sha512', $salt . $password, true);
do {
$hash = \hash('sha512', $hash . $password, true);
} while (--$count);

$output = \mb_substr($settings, 0, 12, '8bit');
$hash_encode64 = static function ($input, $count, &$itoa64) {
$output = '';
$i = 0;

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

$output .= $hash_encode64($hash, 64, $this->itoa64);
$output = $this->hashPhpass($password, $settings);

return \mb_substr($output, 0, 55, '8bit');
}
Expand All @@ -105,17 +47,24 @@ public function verify(string $password, string $hash): bool
*/
public function hash(string $password): string
{
$settings = '$S$D';
$settings .= Hex::encode(\random_bytes(4));
$salt = Hex::encode(\random_bytes(4));

return $this->hashDrupal($password, $settings) . ':';
return $this->hashDrupal($password, $this->getSettings() . $salt) . ':';
}

/**
* @inheritDoc
*/
public function needsRehash(string $hash): bool
{
return false;
return !\str_starts_with($hash, $this->getSettings());
}

/**
* Returns the settings prefix with the algorithm identifier and costs.
*/
private function getSettings(): string
{
return '$S$' . $this->itoa64[self::COSTS];
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace wcf\system\user\authentication\password\algorithm;

use ParagonIE\ConstantTime\Hex;
use wcf\system\user\authentication\password\IPasswordAlgorithm;

/**
Expand All @@ -16,4 +17,32 @@
final class Phpass implements IPasswordAlgorithm
{
use TPhpass;

private const COSTS = 10;

/**
* @inheritDoc
*/
public function hash(string $password): string
{
$salt = Hex::encode(\random_bytes(4));

return $this->hashPhpass($password, $this->getSettings() . $salt) . ':';
}

/**
* @inheritDoc
*/
public function needsRehash(string $hash): bool
{
return !\str_starts_with($hash, $this->getSettings());
}

/**
* Returns the settings prefix with the algorithm identifier and costs.
*/
private function getSettings(): string
{
return '$H$' . $this->itoa64[self::COSTS];
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,6 @@ final class Phpbb3 implements IPasswordAlgorithm
{
use TPhpass {
verify as phpassVerify;

hash as phpassHash;
}

public function verify(string $password, string $hash): bool
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,23 @@ private function hashPhpass(string $password, string $settings): string
$output = '*';

// Check for correct hash
if (\mb_substr($settings, 0, 3, '8bit') !== '$H$' && \mb_substr($settings, 0, 3, '8bit') !== '$P$') {
if ($settings[0] !== '$' || $settings[2] !== '$') {
return $output;
}

$variant = $settings[1];
switch ($variant) {
case 'H':
case 'P':
$algo = 'md5';
break;
case 'S':
$algo = 'sha512';
break;
default:
return $output;
}

$count_log2 = \mb_strpos($this->itoa64, $settings[3], 0, '8bit');

if ($count_log2 < 7 || $count_log2 > 30) {
Expand All @@ -42,47 +55,51 @@ private function hashPhpass(string $password, string $settings): string
return $output;
}

$hash = \md5($salt . $password, true);
$hash = \hash($algo, $salt . $password, true);
do {
$hash = \md5($hash . $password, true);
$hash = \hash($algo, $hash . $password, true);
} while (--$count);

$output = \mb_substr($settings, 0, 12, '8bit');
$hash_encode64 = static function ($input, $count, &$itoa64) {
$output = '';
$i = 0;
$output .= $this->encode64($hash, \mb_strlen($hash, '8bit'));

do {
$value = \ord($input[$i++]);
$output .= $itoa64[$value & 0x3f];
return $output;
}

if ($i < $count) {
$value |= \ord($input[$i]) << 8;
}
/**
* Encodes $count characters from $input with PHPASS' custom base64 encoder.
*/
private function encode64(string $input, int $count): string
{
$output = '';
$i = 0;

$output .= $itoa64[($value >> 6) & 0x3f];
do {
$value = \ord($input[$i++]);
$output .= $this->itoa64[$value & 0x3f];

if ($i++ >= $count) {
break;
}
if ($i < $count) {
$value |= \ord($input[$i]) << 8;
}

if ($i < $count) {
$value |= \ord($input[$i]) << 16;
}
$output .= $this->itoa64[($value >> 6) & 0x3f];

$output .= $itoa64[($value >> 12) & 0x3f];
if ($i++ >= $count) {
break;
}

if ($i++ >= $count) {
break;
}
if ($i < $count) {
$value |= \ord($input[$i]) << 16;
}

$output .= $itoa64[($value >> 18) & 0x3f];
} while ($i < $count);
$output .= $this->itoa64[($value >> 12) & 0x3f];

return $output;
};
if ($i++ >= $count) {
break;
}

$output .= $hash_encode64($hash, 16, $this->itoa64);
$output .= $this->itoa64[($value >> 18) & 0x3f];
} while ($i < $count);

return $output;
}
Expand All @@ -104,7 +121,7 @@ public function verify(string $password, string $hash): bool
}

/**
* @inheritDoc
* @deprecated 5.5 Use Phpass::hash() instead.
*/
public function hash(string $password): string
{
Expand All @@ -115,7 +132,7 @@ public function hash(string $password): string
}

/**
* @inheritDoc
* @deprecated 5.5 Use Phpass::needsRehash() instead.
*/
public function needsRehash(string $hash): bool
{
Expand Down