‪TYPO3CMS  ‪main
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 
44  public function ‪__construct(array ‪$options = [])
45  {
46  $newOptions = ‪$this->options;
47  if (isset(‪$options['hash_count'])) {
48  if ((int)‪$options['hash_count'] < 1000 || (int)‪$options['hash_count'] > 10000000) {
49  throw new \InvalidArgumentException(
50  'hash_count must not be lower than 1000 or bigger than 10000000',
51  1533903544
52  );
53  }
54  $newOptions['hash_count'] = (int)‪$options['hash_count'];
55  }
56  $this->options = $newOptions;
57  }
58 
67  public function ‪checkPassword(string $plainPW, string $saltedHashPW): bool
68  {
69  return $this->‪isValidSalt($saltedHashPW) && hash_equals((string)$this->‪getHashedPasswordInternal($plainPW, $saltedHashPW), $saltedHashPW);
70  }
71 
77  public function ‪isAvailable(): bool
78  {
79  return true;
80  }
81 
82  public function ‪getHashedPassword(string $password): ?string
83  {
84  return $this->‪getHashedPasswordInternal($password);
85  }
86 
93  public function ‪isValidSaltedPW(string $saltedPW): bool
94  {
95  $isValid = !strncmp(self::PREFIX, $saltedPW, strlen(self::PREFIX));
96  if ($isValid) {
97  $isValid = $this->‪isValidSalt($saltedPW);
98  }
99  return $isValid;
100  }
101 
112  public function ‪isHashUpdateNeeded(string $saltedPW): bool
113  {
114  // Check whether this was an updated password.
115  if (strncmp($saltedPW, self::PREFIX, strlen(self::PREFIX)) || !$this->‪isValidSalt($saltedPW)) {
116  return true;
117  }
118  // Check whether the iteration count used differs from the standard number.
119  $iterationCount = $this->‪getIterationCount($saltedPW);
120  return $iterationCount !== null && $iterationCount < $this->options['hash_count'];
121  }
122 
129  protected function ‪getIterationCount(string $setting)
130  {
131  $iterationCount = null;
132  $setting = substr($setting, strlen(self::PREFIX));
133  $firstSplitPos = strpos($setting, '$');
134  // Hashcount existing
135  if ($firstSplitPos !== false
136  && $firstSplitPos <= strlen((string)10000000)
137  && is_numeric(substr($setting, 0, $firstSplitPos))
138  ) {
139  $iterationCount = (int)substr($setting, 0, $firstSplitPos);
140  }
141  return $iterationCount;
142  }
143 
151  protected function ‪getHashedPasswordInternal(string $password, string $salt = null)
152  {
153  $saltedPW = null;
154  if ($password !== '') {
155  $hashCount = $this->options['hash_count'];
156  if (empty($salt) || !$this->‪isValidSalt($salt)) {
157  $salt = $this->‪getGeneratedSalt();
158  } else {
159  $hashCount = $this->‪getIterationCount($salt);
160  $salt = $this->‪getStoredSalt($salt);
161  }
162  $hash = hash_pbkdf2('sha256', $password, $salt, $hashCount, 0, true);
163  $saltWithSettings = $salt;
164  // salt without setting
165  if (strlen($salt) === 16) {
166  $saltWithSettings = self::PREFIX . sprintf('%02u', $hashCount) . '$' . $this->‪base64Encode($salt, 16);
167  }
168  $saltedPW = $saltWithSettings . '$' . $this->‪base64Encode($hash, strlen($hash));
169  }
170  return $saltedPW;
171  }
172 
184  protected function ‪getGeneratedSalt(): string
185  {
186  return GeneralUtility::makeInstance(Random::class)->generateRandomBytes(16);
187  }
188 
193  protected function ‪getStoredSalt(string $salt): string
194  {
195  if (!strncmp('$', $salt, 1)) {
196  if (!strncmp(self::PREFIX, $salt, strlen(self::PREFIX))) {
197  $saltParts = ‪GeneralUtility::trimExplode('$', $salt, true);
198  $salt = $saltParts[2];
199  }
200  }
201  return $this->‪base64Decode($salt);
202  }
203 
209  protected function ‪getItoa64(): string
210  {
211  return './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
212  }
213 
220  protected function ‪isValidSalt(string $salt): bool
221  {
222  $isValid = ($skip = false);
223  $reqLenBase64 = $this->‪getLengthBase64FromBytes(16);
224  if (strlen($salt) >= $reqLenBase64) {
225  // Salt with prefixed setting
226  if (!strncmp('$', $salt, 1)) {
227  if (!strncmp(self::PREFIX, $salt, strlen(self::PREFIX))) {
228  $isValid = true;
229  $salt = substr($salt, (int)strrpos($salt, '$') + 1);
230  } else {
231  $skip = true;
232  }
233  }
234  // Checking base64 characters
235  if (!$skip && strlen($salt) >= $reqLenBase64) {
236  if (preg_match('/^[' . preg_quote($this->‪getItoa64(), '/') . ']{' . $reqLenBase64 . ',' . $reqLenBase64 . '}$/', substr($salt, 0, $reqLenBase64))) {
237  $isValid = true;
238  }
239  }
240  }
241  return $isValid;
242  }
243 
251  protected function ‪getLengthBase64FromBytes(int $byteLength): int
252  {
253  // Calculates bytes in bits in base64
254  return (int)ceil($byteLength * 8 / 6);
255  }
256 
265  protected function ‪base64Encode(string $input, int $count): string
266  {
267  $input = substr($input, 0, $count);
268  return rtrim(str_replace('+', '.', base64_encode($input)), " =\r\n\t\0\x0B");
269  }
270 
275  protected function ‪base64Decode(string $value): string
276  {
277  return base64_decode(str_replace('.', '+', $value));
278  }
279 }
‪TYPO3\CMS\Core\Crypto\PasswordHashing\Pbkdf2PasswordHash\getStoredSalt
‪getStoredSalt(string $salt)
Definition: Pbkdf2PasswordHash.php:192
‪TYPO3\CMS\Core\Crypto\PasswordHashing\Pbkdf2PasswordHash\getGeneratedSalt
‪string getGeneratedSalt()
Definition: Pbkdf2PasswordHash.php:183
‪TYPO3\CMS\Core\Crypto\PasswordHashing\Pbkdf2PasswordHash\base64Encode
‪string base64Encode(string $input, int $count)
Definition: Pbkdf2PasswordHash.php:264
‪TYPO3\CMS\Core\Crypto\PasswordHashing
Definition: AbstractArgon2PasswordHash.php:18
‪TYPO3\CMS\Core\Crypto\PasswordHashing\Pbkdf2PasswordHash\getLengthBase64FromBytes
‪int getLengthBase64FromBytes(int $byteLength)
Definition: Pbkdf2PasswordHash.php:250
‪TYPO3\CMS\Core\Crypto\PasswordHashing\Pbkdf2PasswordHash\getHashedPassword
‪getHashedPassword(string $password)
Definition: Pbkdf2PasswordHash.php:81
‪TYPO3\CMS\Core\Crypto\PasswordHashing\Pbkdf2PasswordHash\isAvailable
‪bool isAvailable()
Definition: Pbkdf2PasswordHash.php:76
‪TYPO3\CMS\Core\Crypto\PasswordHashing\Pbkdf2PasswordHash\isValidSaltedPW
‪bool isValidSaltedPW(string $saltedPW)
Definition: Pbkdf2PasswordHash.php:92
‪TYPO3\CMS\Core\Crypto\PasswordHashing\Pbkdf2PasswordHash\checkPassword
‪bool checkPassword(string $plainPW, string $saltedHashPW)
Definition: Pbkdf2PasswordHash.php:66
‪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:128
‪TYPO3\CMS\Core\Crypto\PasswordHashing\Pbkdf2PasswordHash\PREFIX
‪const PREFIX
Definition: Pbkdf2PasswordHash.php:32
‪TYPO3\CMS\Core\Crypto\PasswordHashing\Pbkdf2PasswordHash\base64Decode
‪base64Decode(string $value)
Definition: Pbkdf2PasswordHash.php:274
‪TYPO3\CMS\Core\Crypto\PasswordHashing\Pbkdf2PasswordHash\__construct
‪__construct(array $options=[])
Definition: Pbkdf2PasswordHash.php:43
‪TYPO3\CMS\Core\Crypto\PasswordHashing\Pbkdf2PasswordHash\isValidSalt
‪bool isValidSalt(string $salt)
Definition: Pbkdf2PasswordHash.php:219
‪TYPO3\CMS\Core\Crypto\PasswordHashing\Pbkdf2PasswordHash\getItoa64
‪string getItoa64()
Definition: Pbkdf2PasswordHash.php:208
‪TYPO3\CMS\Core\Crypto\PasswordHashing\Pbkdf2PasswordHash\isHashUpdateNeeded
‪bool isHashUpdateNeeded(string $saltedPW)
Definition: Pbkdf2PasswordHash.php:111
‪TYPO3\CMS\Core\Crypto\Random
Definition: Random.php:27
‪TYPO3\CMS\Core\Crypto\PasswordHashing\Pbkdf2PasswordHash\$options
‪array $options
Definition: Pbkdf2PasswordHash.php:36
‪TYPO3\CMS\Core\Utility\GeneralUtility
Definition: GeneralUtility.php:52
‪TYPO3\CMS\Core\Crypto\PasswordHashing\Pbkdf2PasswordHash\getHashedPasswordInternal
‪string null getHashedPasswordInternal(string $password, string $salt=null)
Definition: Pbkdf2PasswordHash.php:150
‪TYPO3\CMS\Core\Crypto\PasswordHashing\PasswordHashInterface
Definition: PasswordHashInterface.php:25
‪TYPO3\CMS\Core\Utility\GeneralUtility\trimExplode
‪static list< string > trimExplode(string $delim, string $string, bool $removeEmptyValues=false, int $limit=0)
Definition: GeneralUtility.php:817