‪TYPO3CMS  10.4
Pbkdf2PasswordHash.php
Go to the documentation of this file.
1 <?php
2 
3 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 
19 
22 
28 {
32  protected const ‪PREFIX = '$pbkdf2-sha256$';
33 
37  protected ‪$options = [
38  'hash_count' => 25000
39  ];
40 
46  public function ‪__construct(array ‪$options = [])
47  {
48  $newOptions = ‪$this->options;
49  if (isset(‪$options['hash_count'])) {
50  if ((int)‪$options['hash_count'] < 1000 || (int)‪$options['hash_count'] > 10000000) {
51  throw new \InvalidArgumentException(
52  'hash_count must not be lower than 1000 or bigger than 10000000',
53  1533903544
54  );
55  }
56  $newOptions['hash_count'] = (int)‪$options['hash_count'];
57  }
58  $this->options = $newOptions;
59  }
60 
69  public function ‪checkPassword(string $plainPW, string $saltedHashPW): bool
70  {
71  return $this->‪isValidSalt($saltedHashPW) && hash_equals((string)$this->‪getHashedPasswordInternal($plainPW, $saltedHashPW), $saltedHashPW);
72  }
73 
79  public function ‪isAvailable(): bool
80  {
81  return function_exists('hash_pbkdf2');
82  }
83 
90  public function ‪getHashedPassword(string $password)
91  {
92  return $this->‪getHashedPasswordInternal($password);
93  }
94 
101  public function ‪isValidSaltedPW(string $saltedPW): bool
102  {
103  $isValid = !strncmp(self::PREFIX, $saltedPW, strlen(self::PREFIX));
104  if ($isValid) {
105  $isValid = $this->‪isValidSalt($saltedPW);
106  }
107  return $isValid;
108  }
109 
120  public function ‪isHashUpdateNeeded(string $saltedPW): bool
121  {
122  // Check whether this was an updated password.
123  if (strncmp($saltedPW, self::PREFIX, strlen(self::PREFIX)) || !$this->‪isValidSalt($saltedPW)) {
124  return true;
125  }
126  // Check whether the iteration count used differs from the standard number.
127  $iterationCount = $this->‪getIterationCount($saltedPW);
128  return $iterationCount !== null && $iterationCount < $this->options['hash_count'];
129  }
130 
137  protected function ‪getIterationCount(string $setting)
138  {
139  $iterationCount = null;
140  $setting = substr($setting, strlen(self::PREFIX));
141  $firstSplitPos = strpos($setting, '$');
142  // Hashcount existing
143  if ($firstSplitPos !== false
144  && $firstSplitPos <= strlen((string)10000000)
145  && is_numeric(substr($setting, 0, $firstSplitPos))
146  ) {
147  $iterationCount = (int)substr($setting, 0, $firstSplitPos);
148  }
149  return $iterationCount;
150  }
151 
159  protected function ‪getHashedPasswordInternal(string $password, string $salt = null)
160  {
161  $saltedPW = null;
162  if ($password !== '') {
163  $hashCount = $this->options['hash_count'];
164  if (empty($salt) || !$this->‪isValidSalt($salt)) {
165  $salt = $this->‪getGeneratedSalt();
166  } else {
167  $hashCount = $this->‪getIterationCount($salt);
168  $salt = $this->‪getStoredSalt($salt);
169  }
170  $hash = hash_pbkdf2('sha256', $password, $salt, $hashCount, 0, true);
171  $saltWithSettings = $salt;
172  // salt without setting
173  if (strlen($salt) === 16) {
174  $saltWithSettings = self::PREFIX . sprintf('%02u', $hashCount) . '$' . $this->‪base64Encode($salt, 16);
175  }
176  $saltedPW = $saltWithSettings . '$' . $this->‪base64Encode($hash, strlen($hash));
177  }
178  return $saltedPW;
179  }
180 
192  protected function ‪getGeneratedSalt(): string
193  {
194  return GeneralUtility::makeInstance(Random::class)->generateRandomBytes(16);
195  }
196 
204  protected function ‪getStoredSalt(string $salt): string
205  {
206  if (!strncmp('$', $salt, 1)) {
207  if (!strncmp(self::PREFIX, $salt, strlen(self::PREFIX))) {
208  $saltParts = ‪GeneralUtility::trimExplode('$', $salt, 4);
209  $salt = $saltParts[2];
210  }
211  }
212  return $this->‪base64Decode($salt);
213  }
214 
220  protected function ‪getItoa64(): string
221  {
222  return './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
223  }
224 
231  protected function ‪isValidSalt(string $salt): bool
232  {
233  $isValid = ($skip = false);
234  $reqLenBase64 = $this->‪getLengthBase64FromBytes(16);
235  if (strlen($salt) >= $reqLenBase64) {
236  // Salt with prefixed setting
237  if (!strncmp('$', $salt, 1)) {
238  if (!strncmp(self::PREFIX, $salt, strlen(self::PREFIX))) {
239  $isValid = true;
240  $salt = substr($salt, (int)strrpos($salt, '$') + 1);
241  } else {
242  $skip = true;
243  }
244  }
245  // Checking base64 characters
246  if (!$skip && strlen($salt) >= $reqLenBase64) {
247  if (preg_match('/^[' . preg_quote($this->‪getItoa64(), '/') . ']{' . $reqLenBase64 . ',' . $reqLenBase64 . '}$/', substr($salt, 0, $reqLenBase64))) {
248  $isValid = true;
249  }
250  }
251  }
252  return $isValid;
253  }
254 
262  protected function ‪getLengthBase64FromBytes(int $byteLength): int
263  {
264  // Calculates bytes in bits in base64
265  return (int)ceil($byteLength * 8 / 6);
266  }
267 
276  protected function ‪base64Encode(string $input, int $count): string
277  {
278  $input = substr($input, 0, $count);
279  return rtrim(str_replace('+', '.', base64_encode($input)), " =\r\n\t\0\x0B");
280  }
281 
289  protected function ‪base64Decode(string $value): string
290  {
291  return base64_decode(str_replace('.', '+', $value));
292  }
293 }
‪TYPO3\CMS\Core\Crypto\PasswordHashing\Pbkdf2PasswordHash\getHashedPassword
‪string null getHashedPassword(string $password)
Definition: Pbkdf2PasswordHash.php:89
‪TYPO3\CMS\Core\Crypto\PasswordHashing\Pbkdf2PasswordHash\getGeneratedSalt
‪string getGeneratedSalt()
Definition: Pbkdf2PasswordHash.php:191
‪TYPO3\CMS\Core\Crypto\PasswordHashing\Pbkdf2PasswordHash\base64Encode
‪string base64Encode(string $input, int $count)
Definition: Pbkdf2PasswordHash.php:275
‪TYPO3\CMS\Core\Crypto\PasswordHashing
Definition: AbstractArgon2PasswordHash.php:18
‪TYPO3\CMS\Core\Crypto\PasswordHashing\Pbkdf2PasswordHash\getLengthBase64FromBytes
‪int getLengthBase64FromBytes(int $byteLength)
Definition: Pbkdf2PasswordHash.php:261
‪TYPO3\CMS\Core\Crypto\PasswordHashing\Pbkdf2PasswordHash\getStoredSalt
‪string getStoredSalt(string $salt)
Definition: Pbkdf2PasswordHash.php:203
‪TYPO3\CMS\Core\Crypto\PasswordHashing\Pbkdf2PasswordHash\isAvailable
‪bool isAvailable()
Definition: Pbkdf2PasswordHash.php:78
‪TYPO3\CMS\Core\Crypto\PasswordHashing\Pbkdf2PasswordHash\isValidSaltedPW
‪bool isValidSaltedPW(string $saltedPW)
Definition: Pbkdf2PasswordHash.php:100
‪TYPO3\CMS\Core\Crypto\PasswordHashing\Pbkdf2PasswordHash\checkPassword
‪bool checkPassword(string $plainPW, string $saltedHashPW)
Definition: Pbkdf2PasswordHash.php:68
‪TYPO3\CMS\Core\Crypto\PasswordHashing\Pbkdf2PasswordHash\base64Decode
‪string base64Decode(string $value)
Definition: Pbkdf2PasswordHash.php:288
‪TYPO3\CMS\Core\Crypto\PasswordHashing\Pbkdf2PasswordHash
Definition: Pbkdf2PasswordHash.php:28
‪TYPO3\CMS\Core\Crypto\PasswordHashing\Pbkdf2PasswordHash\getIterationCount
‪int null getIterationCount(string $setting)
Definition: Pbkdf2PasswordHash.php:136
‪TYPO3\CMS\Core\Crypto\PasswordHashing\Pbkdf2PasswordHash\PREFIX
‪const PREFIX
Definition: Pbkdf2PasswordHash.php:32
‪TYPO3\CMS\Core\Utility\GeneralUtility\trimExplode
‪static string[] trimExplode($delim, $string, $removeEmptyValues=false, $limit=0)
Definition: GeneralUtility.php:1059
‪TYPO3\CMS\Core\Crypto\PasswordHashing\Pbkdf2PasswordHash\__construct
‪__construct(array $options=[])
Definition: Pbkdf2PasswordHash.php:45
‪TYPO3\CMS\Core\Crypto\PasswordHashing\Pbkdf2PasswordHash\isValidSalt
‪bool isValidSalt(string $salt)
Definition: Pbkdf2PasswordHash.php:230
‪TYPO3\CMS\Core\Crypto\PasswordHashing\Pbkdf2PasswordHash\getItoa64
‪string getItoa64()
Definition: Pbkdf2PasswordHash.php:219
‪TYPO3\CMS\Core\Crypto\PasswordHashing\Pbkdf2PasswordHash\isHashUpdateNeeded
‪bool isHashUpdateNeeded(string $saltedPW)
Definition: Pbkdf2PasswordHash.php:119
‪TYPO3\CMS\Core\Crypto\Random
Definition: Random.php:24
‪TYPO3\CMS\Core\Crypto\PasswordHashing\Pbkdf2PasswordHash\$options
‪array $options
Definition: Pbkdf2PasswordHash.php:36
‪TYPO3\CMS\Core\Utility\GeneralUtility
Definition: GeneralUtility.php:46
‪TYPO3\CMS\Core\Crypto\PasswordHashing\Pbkdf2PasswordHash\getHashedPasswordInternal
‪string null getHashedPasswordInternal(string $password, string $salt=null)
Definition: Pbkdf2PasswordHash.php:158
‪TYPO3\CMS\Core\Crypto\PasswordHashing\PasswordHashInterface
Definition: PasswordHashInterface.php:25