Skip to content

Commit 06a62a2

Browse files
authored
Merge pull request #4762 from WoltLab/password-cleanup
Cleanup PHPASS password implementations
2 parents 1c8ef7e + 01eec81 commit 06a62a2

File tree

4 files changed

+91
-98
lines changed

4 files changed

+91
-98
lines changed

wcfsetup/install/files/lib/system/user/authentication/password/algorithm/Drupal8.class.php

+15-66
Original file line numberDiff line numberDiff line change
@@ -16,74 +16,16 @@
1616
*/
1717
final class Drupal8 implements IPasswordAlgorithm
1818
{
19-
private $itoa64 = './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
19+
use TPhpass;
20+
21+
private const COSTS = 15;
2022

2123
/**
2224
* Returns the hashed password, with the given settings.
2325
*/
2426
private function hashDrupal(string $password, string $settings): string
2527
{
26-
$output = '*';
27-
28-
// Check for correct hash
29-
if (\mb_substr($settings, 0, 3, '8bit') !== '$S$') {
30-
return $output;
31-
}
32-
33-
$count_log2 = \mb_strpos($this->itoa64, $settings[3], 0, '8bit');
34-
35-
if ($count_log2 < 7 || $count_log2 > 30) {
36-
return $output;
37-
}
38-
39-
$count = 1 << $count_log2;
40-
$salt = \mb_substr($settings, 4, 8, '8bit');
41-
42-
if (\mb_strlen($salt, '8bit') != 8) {
43-
return $output;
44-
}
45-
46-
$hash = \hash('sha512', $salt . $password, true);
47-
do {
48-
$hash = \hash('sha512', $hash . $password, true);
49-
} while (--$count);
50-
51-
$output = \mb_substr($settings, 0, 12, '8bit');
52-
$hash_encode64 = static function ($input, $count, &$itoa64) {
53-
$output = '';
54-
$i = 0;
55-
56-
do {
57-
$value = \ord($input[$i++]);
58-
$output .= $itoa64[$value & 0x3f];
59-
60-
if ($i < $count) {
61-
$value |= \ord($input[$i]) << 8;
62-
}
63-
64-
$output .= $itoa64[($value >> 6) & 0x3f];
65-
66-
if ($i++ >= $count) {
67-
break;
68-
}
69-
70-
if ($i < $count) {
71-
$value |= \ord($input[$i]) << 16;
72-
}
73-
74-
$output .= $itoa64[($value >> 12) & 0x3f];
75-
76-
if ($i++ >= $count) {
77-
break;
78-
}
79-
80-
$output .= $itoa64[($value >> 18) & 0x3f];
81-
} while ($i < $count);
82-
83-
return $output;
84-
};
85-
86-
$output .= $hash_encode64($hash, 64, $this->itoa64);
28+
$output = $this->hashPhpass($password, $settings);
8729

8830
return \mb_substr($output, 0, 55, '8bit');
8931
}
@@ -105,17 +47,24 @@ public function verify(string $password, string $hash): bool
10547
*/
10648
public function hash(string $password): string
10749
{
108-
$settings = '$S$D';
109-
$settings .= Hex::encode(\random_bytes(4));
50+
$salt = Hex::encode(\random_bytes(4));
11051

111-
return $this->hashDrupal($password, $settings) . ':';
52+
return $this->hashDrupal($password, $this->getSettings() . $salt) . ':';
11253
}
11354

11455
/**
11556
* @inheritDoc
11657
*/
11758
public function needsRehash(string $hash): bool
11859
{
119-
return false;
60+
return !\str_starts_with($hash, $this->getSettings());
61+
}
62+
63+
/**
64+
* Returns the settings prefix with the algorithm identifier and costs.
65+
*/
66+
private function getSettings(): string
67+
{
68+
return '$S$' . $this->itoa64[self::COSTS];
12069
}
12170
}

wcfsetup/install/files/lib/system/user/authentication/password/algorithm/Phpass.class.php

+29
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

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

5+
use ParagonIE\ConstantTime\Hex;
56
use wcf\system\user\authentication\password\IPasswordAlgorithm;
67

78
/**
@@ -16,4 +17,32 @@
1617
final class Phpass implements IPasswordAlgorithm
1718
{
1819
use TPhpass;
20+
21+
private const COSTS = 10;
22+
23+
/**
24+
* @inheritDoc
25+
*/
26+
public function hash(string $password): string
27+
{
28+
$salt = Hex::encode(\random_bytes(4));
29+
30+
return $this->hashPhpass($password, $this->getSettings() . $salt) . ':';
31+
}
32+
33+
/**
34+
* @inheritDoc
35+
*/
36+
public function needsRehash(string $hash): bool
37+
{
38+
return !\str_starts_with($hash, $this->getSettings());
39+
}
40+
41+
/**
42+
* Returns the settings prefix with the algorithm identifier and costs.
43+
*/
44+
private function getSettings(): string
45+
{
46+
return '$H$' . $this->itoa64[self::COSTS];
47+
}
1948
}

wcfsetup/install/files/lib/system/user/authentication/password/algorithm/Phpbb3.class.php

-2
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,6 @@ final class Phpbb3 implements IPasswordAlgorithm
1818
{
1919
use TPhpass {
2020
verify as phpassVerify;
21-
22-
hash as phpassHash;
2321
}
2422

2523
public function verify(string $password, string $hash): bool

wcfsetup/install/files/lib/system/user/authentication/password/algorithm/TPhpass.class.php

+47-30
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,23 @@ private function hashPhpass(string $password, string $settings): string
2525
$output = '*';
2626

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

32+
$variant = $settings[1];
33+
switch ($variant) {
34+
case 'H':
35+
case 'P':
36+
$algo = 'md5';
37+
break;
38+
case 'S':
39+
$algo = 'sha512';
40+
break;
41+
default:
42+
return $output;
43+
}
44+
3245
$count_log2 = \mb_strpos($this->itoa64, $settings[3], 0, '8bit');
3346

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

45-
$hash = \md5($salt . $password, true);
58+
$hash = \hash($algo, $salt . $password, true);
4659
do {
47-
$hash = \md5($hash . $password, true);
60+
$hash = \hash($algo, $hash . $password, true);
4861
} while (--$count);
4962

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

55-
do {
56-
$value = \ord($input[$i++]);
57-
$output .= $itoa64[$value & 0x3f];
66+
return $output;
67+
}
5868

59-
if ($i < $count) {
60-
$value |= \ord($input[$i]) << 8;
61-
}
69+
/**
70+
* Encodes $count characters from $input with PHPASS' custom base64 encoder.
71+
*/
72+
private function encode64(string $input, int $count): string
73+
{
74+
$output = '';
75+
$i = 0;
6276

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

65-
if ($i++ >= $count) {
66-
break;
67-
}
81+
if ($i < $count) {
82+
$value |= \ord($input[$i]) << 8;
83+
}
6884

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

73-
$output .= $itoa64[($value >> 12) & 0x3f];
87+
if ($i++ >= $count) {
88+
break;
89+
}
7490

75-
if ($i++ >= $count) {
76-
break;
77-
}
91+
if ($i < $count) {
92+
$value |= \ord($input[$i]) << 16;
93+
}
7894

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

82-
return $output;
83-
};
97+
if ($i++ >= $count) {
98+
break;
99+
}
84100

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

87104
return $output;
88105
}
@@ -104,7 +121,7 @@ public function verify(string $password, string $hash): bool
104121
}
105122

106123
/**
107-
* @inheritDoc
124+
* @deprecated 5.5 Use Phpass::hash() instead.
108125
*/
109126
public function hash(string $password): string
110127
{
@@ -115,7 +132,7 @@ public function hash(string $password): string
115132
}
116133

117134
/**
118-
* @inheritDoc
135+
* @deprecated 5.5 Use Phpass::needsRehash() instead.
119136
*/
120137
public function needsRehash(string $hash): bool
121138
{

0 commit comments

Comments
 (0)