1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
|
<?php
namespace r7r\cms\sys;
/**
* Functions for creating and checking password hashes. Mostly wrappers around php's builtin password_\* functions but
* can also verify our old legacy password hash.
*/
class PasswordHash
{
private const PASSWORD_ALGO = \PASSWORD_DEFAULT;
/** @var bool */
private $isLegacy;
/** @var string */
private $hashData;
private function __construct(bool $isLegacy, string $hashData)
{
$this->isLegacy = $isLegacy;
$this->hashData = $hashData;
}
private static function verifyLegacy(string $password, string $pwhash): bool
{
list($iterations, $hexsalt) = explode('$', $pwhash);
return self::hashLegacy($password, pack("H*", $hexsalt), $iterations) == $pwhash;
}
private static function hashLegacy(string $data, $salt, string $iterations): string
{
$hash = $data . $salt;
for ($i = $iterations ;$i--;) {
$hash = sha1($data . $hash . $salt, (bool) $i);
}
return $iterations . '$' . bin2hex($salt) . '$' . $hash;
}
private function format(): string
{
return $this->isLegacy
? $this->hashData
: '!' . $this->hashData;
}
private static function parse(string $s): self
{
return substr($s, 0, 1) === '!'
? new self(false, substr($s, 1))
: new self(true, $s);
}
/**
* Verifies that a given password is valid for the given hash
* @param string $password
* @param string $hash
* @return bool
*/
public static function verify(string $password, string $hash): bool
{
$hash = self::parse($hash);
return $hash->isLegacy
? self::verifyLegacy($password, $hash->hashData)
: password_verify($password, $hash->hashData);
}
/**
* Creates a hash for a password
* @param string $password
* @return string Treat this as opaque data. Don't rely on it being in a certain format, it might change in the future.
*/
public static function hash(string $password): string
{
return (new self(false, password_hash($password, self::PASSWORD_ALGO)))->format();
}
/**
* Checks, if a given hash should be recomputed (because it's not considered secure any more) if the password is known.
* @param string $hash
* @return bool
*/
public static function needsRehash(string $hash): bool
{
$hash = self::parse($hash);
return $hash->isLegacy
? true
: password_needs_rehash($hash->hashData, self::PASSWORD_ALGO);
}
}
|