‪TYPO3CMS  9.5
Pbkdf2PasswordHash.php
Go to the documentation of this file.
1 <?php
2 declare(strict_types = 1);
4 
5 /*
6  * This file is part of the TYPO3 CMS project.
7  *
8  * It is free software; you can redistribute it and/or modify it under
9  * the terms of the GNU General Public License, either version 2
10  * of the License, or any later version.
11  *
12  * For the full copyright and license information, please read the
13  * LICENSE.txt file that was distributed with this source code.
14  *
15  * The TYPO3 project - inspiring people to share!
16  */
17 
21 
27 {
29 
33  private ‪$deprecatedPublicMethods = [
34  'isValidSalt' => 'Using Pbkdf2PasswordHash::isValidSalt() is deprecated and will not be possible anymore in TYPO3 v10.0.',
35  'base64Encode' => 'Using Pbkdf2PasswordHash::base64Encode() is deprecated and will not be possible anymore in TYPO3 v10.0.',
36  'base64Decode' => 'Using Pbkdf2PasswordHash::base64Decode() is deprecated and will not be possible anymore in TYPO3 v10.0.',
37  ];
38 
42  protected const ‪PREFIX = '$pbkdf2-sha256$';
43 
47  protected ‪$options = [
48  'hash_count' => 25000
49  ];
50 
57  const ‪ITOA64 = './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
58 
64  const ‪HASH_COUNT = 25000;
65 
71  const ‪MAX_HASH_COUNT = 10000000;
72 
78  const ‪MIN_HASH_COUNT = 1000;
79 
85  public function ‪__construct(array ‪$options = [])
86  {
87  $newOptions = ‪$this->options;
88  if (isset(‪$options['hash_count'])) {
89  if ((int)‪$options['hash_count'] < 1000 || (int)‪$options['hash_count'] > 10000000) {
90  throw new \InvalidArgumentException(
91  'hash_count must not be lower than 1000 or bigger than 10000000',
92  1533903544
93  );
94  }
95  $newOptions['hash_count'] = (int)‪$options['hash_count'];
96  }
97  $this->options = $newOptions;
98  }
99 
108  public function ‪checkPassword(string $plainPW, string $saltedHashPW): bool
109  {
110  return $this->‪isValidSalt($saltedHashPW) && hash_equals($this->‪getHashedPasswordInternal($plainPW, $saltedHashPW), $saltedHashPW);
111  }
112 
118  public function ‪isAvailable(): bool
119  {
120  return function_exists('hash_pbkdf2');
121  }
122 
130  public function ‪getHashedPassword(string $password, string $salt = null)
131  {
132  if ($salt !== null) {
133  trigger_error(static::class . ': using a custom salt is deprecated.', E_USER_DEPRECATED);
134  }
135  return $this->‪getHashedPasswordInternal($password, $salt);
136  }
137 
144  public function ‪isValidSaltedPW(string $saltedPW): bool
145  {
146  $isValid = !strncmp(self::PREFIX, $saltedPW, strlen(self::PREFIX));
147  if ($isValid) {
148  $isValid = $this->‪isValidSalt($saltedPW);
149  }
150  return $isValid;
151  }
152 
163  public function ‪isHashUpdateNeeded(string $saltedPW): bool
164  {
165  // Check whether this was an updated password.
166  if (strncmp($saltedPW, self::PREFIX, strlen(self::PREFIX)) || !$this->‪isValidSalt($saltedPW)) {
167  return true;
168  }
169  // Check whether the iteration count used differs from the standard number.
170  $iterationCount = $this->‪getIterationCount($saltedPW);
171  return $iterationCount !== null && $iterationCount < $this->options['hash_count'];
172  }
173 
180  protected function ‪getIterationCount(string $setting)
181  {
182  $iterationCount = null;
183  $setting = substr($setting, strlen(self::PREFIX));
184  $firstSplitPos = strpos($setting, '$');
185  // Hashcount existing
186  if ($firstSplitPos !== false
187  && $firstSplitPos <= strlen((string)10000000)
188  && is_numeric(substr($setting, 0, $firstSplitPos))
189  ) {
190  $iterationCount = (int)substr($setting, 0, $firstSplitPos);
191  }
192  return $iterationCount;
193  }
194 
202  protected function ‪getHashedPasswordInternal(string $password, string $salt = null)
203  {
204  $saltedPW = null;
205  if ($password !== '') {
206  $hashCount = $this->options['hash_count'];
207  if (empty($salt) || !$this->‪isValidSalt($salt)) {
208  $salt = $this->‪getGeneratedSalt();
209  } else {
210  $hashCount = $this->‪getIterationCount($salt);
211  $salt = $this->‪getStoredSalt($salt);
212  }
213  $hash = hash_pbkdf2('sha256', $password, $salt, $hashCount, 0, true);
214  $saltWithSettings = $salt;
215  // salt without setting
216  if (strlen($salt) === 16) {
217  $saltWithSettings = self::PREFIX . sprintf('%02u', $hashCount) . '$' . $this->‪base64Encode($salt, 16);
218  }
219  $saltedPW = $saltWithSettings . '$' . $this->‪base64Encode($hash, strlen($hash));
220  }
221  return $saltedPW;
222  }
223 
235  protected function ‪getGeneratedSalt(): string
236  {
237  return GeneralUtility::makeInstance(Random::class)->generateRandomBytes(16);
238  }
239 
247  protected function ‪getStoredSalt(string $salt): string
248  {
249  if (!strncmp('$', $salt, 1)) {
250  if (!strncmp(self::PREFIX, $salt, strlen(self::PREFIX))) {
251  $saltParts = GeneralUtility::trimExplode('$', $salt, 4);
252  $salt = $saltParts[2];
253  }
254  }
255  return $this->‪base64Decode($salt);
256  }
257 
263  protected function ‪getItoa64(): string
264  {
265  return './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
266  }
267 
274  protected function ‪isValidSalt(string $salt): bool
275  {
276  $isValid = ($skip = false);
277  $reqLenBase64 = $this->‪getLengthBase64FromBytes(16);
278  if (strlen($salt) >= $reqLenBase64) {
279  // Salt with prefixed setting
280  if (!strncmp('$', $salt, 1)) {
281  if (!strncmp(self::PREFIX, $salt, strlen(self::PREFIX))) {
282  $isValid = true;
283  $salt = substr($salt, strrpos($salt, '$') + 1);
284  } else {
285  $skip = true;
286  }
287  }
288  // Checking base64 characters
289  if (!$skip && strlen($salt) >= $reqLenBase64) {
290  if (preg_match('/^[' . preg_quote($this->‪getItoa64(), '/') . ']{' . $reqLenBase64 . ',' . $reqLenBase64 . '}$/', substr($salt, 0, $reqLenBase64))) {
291  $isValid = true;
292  }
293  }
294  }
295  return $isValid;
296  }
297 
305  protected function ‪getLengthBase64FromBytes(int $byteLength): int
306  {
307  // Calculates bytes in bits in base64
308  return (int)ceil($byteLength * 8 / 6);
309  }
310 
319  protected function ‪base64Encode(string $input, int $count): string
320  {
321  $input = substr($input, 0, $count);
322  return rtrim(str_replace('+', '.', base64_encode($input)), " =\r\n\t\0\x0B");
323  }
324 
332  protected function ‪base64Decode(string $value): string
333  {
334  return base64_decode(str_replace('.', '+', $value));
335  }
336 
343  public function ‪getHashCount(): int
344  {
345  trigger_error('This method will be removed in TYPO3 v10.0.', E_USER_DEPRECATED);
346  return $this->options['hash_count'];
347  }
348 
355  public function ‪getMaxHashCount(): int
356  {
357  trigger_error('This method will be removed in TYPO3 v10.0.', E_USER_DEPRECATED);
358  return 10000000;
359  }
360 
367  public function ‪getMinHashCount(): int
368  {
369  trigger_error('This method will be removed in TYPO3 v10.0.', E_USER_DEPRECATED);
370  return 1000;
371  }
372 
379  public function ‪getSaltLength(): int
380  {
381  trigger_error('This method will be removed in TYPO3 v10.0.', E_USER_DEPRECATED);
382  return 16;
383  }
384 
391  public function ‪getSetting(): string
392  {
393  trigger_error('This method will be removed in TYPO3 v10.0.', E_USER_DEPRECATED);
394  return ‪self::PREFIX;
395  }
396 
403  public function ‪setHashCount(int $hashCount = null)
404  {
405  trigger_error('This method will be removed in TYPO3 v10.0.', E_USER_DEPRECATED);
406  if ($hashCount >= 1000 && $hashCount <= 10000000) {
407  $this->options['hash_count'] = $hashCount;
408  }
409  }
410 
417  public function ‪setMaxHashCount(int $maxHashCount = null)
418  {
419  trigger_error('This method will be removed in TYPO3 v10.0.', E_USER_DEPRECATED);
420  // Empty, max hash count is hard coded to 10000000
421  }
422 
429  public function ‪setMinHashCount(int $minHashCount = null)
430  {
431  trigger_error('This method will be removed in TYPO3 v10.0.', E_USER_DEPRECATED);
432  // Empty, max hash count is hard coded to 1000
433  }
434 }
‪TYPO3\CMS\Core\Crypto\PasswordHashing\Pbkdf2PasswordHash\getGeneratedSalt
‪string getGeneratedSalt()
Definition: Pbkdf2PasswordHash.php:233
‪TYPO3\CMS\Core\Crypto\PasswordHashing\Pbkdf2PasswordHash\base64Encode
‪string base64Encode(string $input, int $count)
Definition: Pbkdf2PasswordHash.php:317
‪TYPO3\CMS\Core\Crypto\PasswordHashing\Pbkdf2PasswordHash\HASH_COUNT
‪const HASH_COUNT
Definition: Pbkdf2PasswordHash.php:62
‪TYPO3\CMS\Core\Crypto\PasswordHashing
Definition: AbstractComposedSalt.php:3
‪TYPO3\CMS\Core\Crypto\PasswordHashing\Pbkdf2PasswordHash\getLengthBase64FromBytes
‪int getLengthBase64FromBytes(int $byteLength)
Definition: Pbkdf2PasswordHash.php:303
‪TYPO3\CMS\Core\Crypto\PasswordHashing\Pbkdf2PasswordHash\setMaxHashCount
‪setMaxHashCount(int $maxHashCount=null)
Definition: Pbkdf2PasswordHash.php:415
‪TYPO3\CMS\Core\Crypto\PasswordHashing\Pbkdf2PasswordHash\getSaltLength
‪int getSaltLength()
Definition: Pbkdf2PasswordHash.php:377
‪TYPO3\CMS\Core\Crypto\PasswordHashing\Pbkdf2PasswordHash\setMinHashCount
‪setMinHashCount(int $minHashCount=null)
Definition: Pbkdf2PasswordHash.php:427
‪TYPO3\CMS\Core\Crypto\PasswordHashing\Pbkdf2PasswordHash\getStoredSalt
‪string getStoredSalt(string $salt)
Definition: Pbkdf2PasswordHash.php:245
‪TYPO3\CMS\Core\Crypto\PasswordHashing\Pbkdf2PasswordHash\ITOA64
‪const ITOA64
Definition: Pbkdf2PasswordHash.php:55
‪TYPO3\CMS\Core\Crypto\PasswordHashing\Pbkdf2PasswordHash\isAvailable
‪bool isAvailable()
Definition: Pbkdf2PasswordHash.php:116
‪TYPO3\CMS\Core\Crypto\PasswordHashing\Pbkdf2PasswordHash\isValidSaltedPW
‪bool isValidSaltedPW(string $saltedPW)
Definition: Pbkdf2PasswordHash.php:142
‪TYPO3\CMS\Core\Crypto\PasswordHashing\Pbkdf2PasswordHash\getMaxHashCount
‪int getMaxHashCount()
Definition: Pbkdf2PasswordHash.php:353
‪TYPO3\CMS\Core\Crypto\PasswordHashing\Pbkdf2PasswordHash\MIN_HASH_COUNT
‪const MIN_HASH_COUNT
Definition: Pbkdf2PasswordHash.php:76
‪TYPO3\CMS\Core\Crypto\PasswordHashing\Pbkdf2PasswordHash\checkPassword
‪bool checkPassword(string $plainPW, string $saltedHashPW)
Definition: Pbkdf2PasswordHash.php:106
‪TYPO3\CMS\Core\Crypto\PasswordHashing\Pbkdf2PasswordHash\getHashedPassword
‪string null getHashedPassword(string $password, string $salt=null)
Definition: Pbkdf2PasswordHash.php:128
‪TYPO3\CMS\Core\Crypto\PasswordHashing\Pbkdf2PasswordHash\base64Decode
‪string base64Decode(string $value)
Definition: Pbkdf2PasswordHash.php:330
‪TYPO3\CMS\Core\Crypto\PasswordHashing\Pbkdf2PasswordHash\getSetting
‪string getSetting()
Definition: Pbkdf2PasswordHash.php:389
‪TYPO3\CMS\Core\Crypto\PasswordHashing\Pbkdf2PasswordHash
Definition: Pbkdf2PasswordHash.php:27
‪TYPO3\CMS\Core\Crypto\PasswordHashing\Pbkdf2PasswordHash\getIterationCount
‪int null getIterationCount(string $setting)
Definition: Pbkdf2PasswordHash.php:178
‪TYPO3\CMS\Core\Compatibility\PublicMethodDeprecationTrait
Definition: PublicMethodDeprecationTrait.php:68
‪TYPO3\CMS\Core\Crypto\PasswordHashing\Pbkdf2PasswordHash\PREFIX
‪const PREFIX
Definition: Pbkdf2PasswordHash.php:41
‪TYPO3\CMS\Core\Crypto\PasswordHashing\Pbkdf2PasswordHash\setHashCount
‪setHashCount(int $hashCount=null)
Definition: Pbkdf2PasswordHash.php:401
‪TYPO3\CMS\Core\Crypto\PasswordHashing\Pbkdf2PasswordHash\__construct
‪__construct(array $options=[])
Definition: Pbkdf2PasswordHash.php:83
‪TYPO3\CMS\Core\Crypto\PasswordHashing\Pbkdf2PasswordHash\isValidSalt
‪bool isValidSalt(string $salt)
Definition: Pbkdf2PasswordHash.php:272
‪TYPO3\CMS\Core\Crypto\PasswordHashing\Pbkdf2PasswordHash\getItoa64
‪string getItoa64()
Definition: Pbkdf2PasswordHash.php:261
‪TYPO3\CMS\Core\Crypto\PasswordHashing\Pbkdf2PasswordHash\isHashUpdateNeeded
‪bool isHashUpdateNeeded(string $saltedPW)
Definition: Pbkdf2PasswordHash.php:161
‪TYPO3\CMS\Core\Crypto\PasswordHashing\Pbkdf2PasswordHash\MAX_HASH_COUNT
‪const MAX_HASH_COUNT
Definition: Pbkdf2PasswordHash.php:69
‪TYPO3\CMS\Core\Crypto\Random
Definition: Random.php:22
‪TYPO3\CMS\Core\Crypto\PasswordHashing\Pbkdf2PasswordHash\$deprecatedPublicMethods
‪array $deprecatedPublicMethods
Definition: Pbkdf2PasswordHash.php:32
‪TYPO3\CMS\Core\Crypto\PasswordHashing\Pbkdf2PasswordHash\$options
‪array $options
Definition: Pbkdf2PasswordHash.php:45
‪TYPO3\CMS\Core\Utility\GeneralUtility
Definition: GeneralUtility.php:45
‪TYPO3\CMS\Core\Crypto\PasswordHashing\Pbkdf2PasswordHash\getHashedPasswordInternal
‪string null getHashedPasswordInternal(string $password, string $salt=null)
Definition: Pbkdf2PasswordHash.php:200
‪TYPO3\CMS\Core\Crypto\PasswordHashing\PasswordHashInterface
Definition: PasswordHashInterface.php:23
‪TYPO3\CMS\Core\Crypto\PasswordHashing\Pbkdf2PasswordHash\getHashCount
‪int getHashCount()
Definition: Pbkdf2PasswordHash.php:341
‪TYPO3\CMS\Core\Crypto\PasswordHashing\Pbkdf2PasswordHash\getMinHashCount
‪int getMinHashCount()
Definition: Pbkdf2PasswordHash.php:365