|
1
|
|
|
<?php |
|
2
|
|
|
/** |
|
3
|
|
|
* Hash.php |
|
4
|
|
|
* |
|
5
|
|
|
* @category AngryBytes |
|
6
|
|
|
* @package Hash |
|
7
|
|
|
* @copyright Copyright (c) 2007-2016 Angry Bytes BV (http://www.angrybytes.com) |
|
8
|
|
|
*/ |
|
9
|
|
|
|
|
10
|
|
|
namespace AngryBytes\Hash; |
|
11
|
|
|
|
|
12
|
|
|
use \InvalidArgumentException; |
|
13
|
|
|
|
|
14
|
|
|
/** |
|
15
|
|
|
* A collection of hash generation and comparison methods. |
|
16
|
|
|
* |
|
17
|
|
|
* This Hash library must receive a \AngryBytes\HasherInterface compatible |
|
18
|
|
|
* instance to work with. |
|
19
|
|
|
* |
|
20
|
|
|
* Providing a salt is strictly optional, and should not be provided when the |
|
21
|
|
|
* hasher provides better salt generation methods. |
|
22
|
|
|
* |
|
23
|
|
|
* @category AngryBytes |
|
24
|
|
|
* @package Hash |
|
25
|
|
|
*/ |
|
26
|
|
|
class Hash |
|
27
|
|
|
{ |
|
28
|
|
|
/** |
|
29
|
|
|
* Salt for hashing |
|
30
|
|
|
* |
|
31
|
|
|
* @var string|null |
|
32
|
|
|
**/ |
|
33
|
|
|
private $salt; |
|
34
|
|
|
|
|
35
|
|
|
/** |
|
36
|
|
|
* Hasher |
|
37
|
|
|
* |
|
38
|
|
|
* @var HasherInterface |
|
39
|
|
|
**/ |
|
40
|
|
|
private $hasher; |
|
41
|
|
|
|
|
42
|
|
|
/** |
|
43
|
|
|
* Constructor |
|
44
|
|
|
* |
|
45
|
|
|
* @param HasherInterface $hasher The hasher to be used |
|
46
|
|
|
* @param string|bool $salt (optional) Omit if the hasher creates its own (better) salt |
|
47
|
|
|
**/ |
|
48
|
|
|
public function __construct(HasherInterface $hasher, $salt = null) |
|
49
|
|
|
{ |
|
50
|
|
|
$this->hasher = $hasher; |
|
51
|
|
|
|
|
52
|
|
|
$this->setSalt($salt); |
|
|
|
|
|
|
53
|
|
|
} |
|
54
|
|
|
|
|
55
|
|
|
/** |
|
56
|
|
|
* Dynamically pass methods to the active hasher |
|
57
|
|
|
* |
|
58
|
|
|
* @param string $method |
|
59
|
|
|
* @param array $parameters |
|
60
|
|
|
*/ |
|
61
|
|
|
public function __call($method, $parameters) |
|
62
|
|
|
{ |
|
63
|
|
|
return $this->hasher->$method(...$parameters); |
|
64
|
|
|
} |
|
65
|
|
|
|
|
66
|
|
|
/** |
|
67
|
|
|
* Get the hasher to use for the actual hashing |
|
68
|
|
|
* |
|
69
|
|
|
* @return HasherInterface |
|
70
|
|
|
*/ |
|
71
|
|
|
public function getHasher() |
|
72
|
|
|
{ |
|
73
|
|
|
return $this->hasher; |
|
74
|
|
|
} |
|
75
|
|
|
|
|
76
|
|
|
/** |
|
77
|
|
|
* Generate a hash |
|
78
|
|
|
* |
|
79
|
|
|
* @param mixed $data The data to hash. This can either be a scalar value or a serializable value. |
|
80
|
|
|
* @param mixed[] $options Additional hasher options (the actual options depend on the registered Hasher) |
|
81
|
|
|
* @return string |
|
82
|
|
|
**/ |
|
83
|
|
|
public function hash($data, array $options = []) |
|
84
|
|
|
{ |
|
85
|
|
|
return $this->hasher->hash( |
|
86
|
|
|
self::getDataString($data), |
|
87
|
|
|
$this->parseHashOptions($options) |
|
88
|
|
|
); |
|
89
|
|
|
} |
|
90
|
|
|
|
|
91
|
|
|
/** |
|
92
|
|
|
* Verify if the data matches the hash |
|
93
|
|
|
* |
|
94
|
|
|
* @param mixed $data The data to verify against the hash string. This can either be a scalar value or a serializable value. |
|
95
|
|
|
* @param string $hash |
|
96
|
|
|
* @param mixed[] $options |
|
97
|
|
|
* @return bool |
|
98
|
|
|
*/ |
|
99
|
|
|
public function verify($data, $hash, array $options = []) |
|
100
|
|
|
{ |
|
101
|
|
|
return $this->hasher->verify( |
|
102
|
|
|
self::getDataString($data), |
|
103
|
|
|
$hash, |
|
104
|
|
|
$this->parseHashOptions($options) |
|
105
|
|
|
); |
|
106
|
|
|
} |
|
107
|
|
|
|
|
108
|
|
|
/** |
|
109
|
|
|
* Hash something into a short hash |
|
110
|
|
|
* |
|
111
|
|
|
* This is simply a shortened version of hash(), returning a 7 character hash, which should be good |
|
112
|
|
|
* enough for identification purposes. Therefore it MUST NOT be used for cryptographic purposes or |
|
113
|
|
|
* password storage but only to create a fast, short string to compare or identify. |
|
114
|
|
|
* |
|
115
|
|
|
* It is RECOMMENDED to only use this method with the Hasher\MD5 hasher as hashes |
|
116
|
|
|
* created by bcrypt/crypt() have a common beginning. |
|
117
|
|
|
* |
|
118
|
|
|
* @see Hash::hash() |
|
119
|
|
|
* |
|
120
|
|
|
* @param string $data |
|
121
|
|
|
* @param mixed[] $options |
|
122
|
|
|
* @return string |
|
123
|
|
|
*/ |
|
124
|
|
|
public function shortHash($data, array $options = []) |
|
125
|
|
|
{ |
|
126
|
|
|
return substr($this->hash($data, $options), 0, 7); |
|
127
|
|
|
} |
|
128
|
|
|
|
|
129
|
|
|
/** |
|
130
|
|
|
* Verify if the data matches the shortHash |
|
131
|
|
|
* |
|
132
|
|
|
* @see Hash::shortHash() |
|
133
|
|
|
* |
|
134
|
|
|
* @param mixed $data |
|
135
|
|
|
* @param string $shortHash |
|
136
|
|
|
* @param mixed[] $options |
|
137
|
|
|
* @return bool |
|
138
|
|
|
**/ |
|
139
|
|
|
public function verifyShortHash($data, $shortHash, array $options = []) |
|
140
|
|
|
{ |
|
141
|
|
|
return self::compare( |
|
142
|
|
|
$this->shortHash($data, $options), |
|
143
|
|
|
$shortHash |
|
144
|
|
|
); |
|
145
|
|
|
} |
|
146
|
|
|
|
|
147
|
|
|
/** |
|
148
|
|
|
* Compare two hashes |
|
149
|
|
|
* |
|
150
|
|
|
* Uses the time-save `hash_equals()` function to compare 2 hashes. |
|
151
|
|
|
* |
|
152
|
|
|
* @param string $hashA |
|
153
|
|
|
* @param string $hashB |
|
154
|
|
|
* @return bool |
|
155
|
|
|
**/ |
|
156
|
|
|
public static function compare($hashA, $hashB) |
|
157
|
|
|
{ |
|
158
|
|
|
return hash_equals($hashA, $hashB); |
|
159
|
|
|
} |
|
160
|
|
|
|
|
161
|
|
|
/** |
|
162
|
|
|
* Set the salt |
|
163
|
|
|
* |
|
164
|
|
|
* @param string|null $salt |
|
165
|
|
|
* @return Hash |
|
166
|
|
|
*/ |
|
167
|
|
|
protected function setSalt($salt) |
|
168
|
|
|
{ |
|
169
|
|
|
if (is_string($salt) && (strlen($salt) < 20 || strlen($salt) > CRYPT_SALT_LENGTH)) { |
|
170
|
|
|
// Make sure it's of sufficient length |
|
171
|
|
|
throw new InvalidArgumentException(sprintf( |
|
172
|
|
|
'Provided salt "%s" does not match the length requirements. A length between 20 en %d characters is required.', |
|
173
|
|
|
$salt, |
|
174
|
|
|
CRYPT_SALT_LENGTH |
|
175
|
|
|
)); |
|
176
|
|
|
} |
|
177
|
|
|
|
|
178
|
|
|
$this->salt = $salt; |
|
179
|
|
|
|
|
180
|
|
|
return $this; |
|
181
|
|
|
} |
|
182
|
|
|
|
|
183
|
|
|
/** |
|
184
|
|
|
* Get the data as a string |
|
185
|
|
|
* |
|
186
|
|
|
* Will serialize non-scalar values |
|
187
|
|
|
* |
|
188
|
|
|
* @param mixed $data |
|
189
|
|
|
* @return string |
|
190
|
|
|
*/ |
|
191
|
|
|
private static function getDataString($data) |
|
192
|
|
|
{ |
|
193
|
|
|
if (is_scalar($data)) { |
|
194
|
|
|
return (string) $data; |
|
195
|
|
|
} |
|
196
|
|
|
|
|
197
|
|
|
return serialize($data); |
|
198
|
|
|
} |
|
199
|
|
|
|
|
200
|
|
|
/** |
|
201
|
|
|
* Merge the default and provided hash options |
|
202
|
|
|
* |
|
203
|
|
|
* Automatically sets the salt as an option when set in this |
|
204
|
|
|
* component. |
|
205
|
|
|
* |
|
206
|
|
|
* @param mixed[] $options |
|
207
|
|
|
* @return mixed[] |
|
208
|
|
|
*/ |
|
209
|
|
|
private function parseHashOptions(array $options = []) |
|
210
|
|
|
{ |
|
211
|
|
|
$defaultOptions = []; |
|
212
|
|
|
|
|
213
|
|
|
// Pass the salt if set |
|
214
|
|
|
if (!is_null($this->salt)) { |
|
215
|
|
|
$defaultOptions['salt'] = $this->salt; |
|
216
|
|
|
} |
|
217
|
|
|
|
|
218
|
|
|
return array_merge($defaultOptions, $options); |
|
219
|
|
|
} |
|
220
|
|
|
} |
|
221
|
|
|
|
This check looks at variables that have been passed in as parameters and are passed out again to other methods.
If the outgoing method call has stricter type requirements than the method itself, an issue is raised.
An additional type check may prevent trouble.