‪TYPO3CMS  ‪main
PhpassPasswordHash.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 
35 {
39  protected const ‪PREFIX = '$P$';
40 
44  protected ‪$options = [
45  'hash_count' => 14,
46  ];
47 
51  public function ‪__construct(array ‪$options = [])
52  {
53  $newOptions = ‪$this->options;
54  if (isset(‪$options['hash_count'])) {
55  if ((int)‪$options['hash_count'] < 7 || (int)‪$options['hash_count'] > 24) {
56  throw new \InvalidArgumentException(
57  'hash_count must not be lower than 7 or bigger than 24',
58  1533940454
59  );
60  }
61  $newOptions['hash_count'] = (int)‪$options['hash_count'];
62  }
63  $this->options = $newOptions;
64  }
65 
74  public function ‪checkPassword(string $plainPW, string $saltedHashPW): bool
75  {
76  $hash = $this->‪cryptPassword($plainPW, $saltedHashPW);
77  return $hash && hash_equals($hash, $saltedHashPW);
78  }
79 
85  public function ‪isAvailable(): bool
86  {
87  return true;
88  }
89 
90  public function ‪getHashedPassword(string $password): ?string
91  {
92  $saltedPW = null;
93  if (!empty($password)) {
94  $salt = $this->‪getGeneratedSalt();
95  $saltedPW = $this->‪cryptPassword($password, $this->‪applySettingsToSalt($salt));
96  }
97  return $saltedPW;
98  }
99 
110  public function ‪isHashUpdateNeeded(string $passString): bool
111  {
112  // Check whether this was an updated password.
113  if (strncmp($passString, '$P$', 3) || strlen($passString) != 34) {
114  return true;
115  }
116  // Check whether the iteration count used differs from the standard number.
117  return $this->‪getCountLog2($passString) < $this->options['hash_count'];
118  }
119 
126  public function ‪isValidSaltedPW(string $saltedPW): bool
127  {
128  $isValid = !strncmp(self::PREFIX, $saltedPW, strlen(self::PREFIX));
129  if ($isValid) {
130  $isValid = $this->‪isValidSalt($saltedPW);
131  }
132  return $isValid;
133  }
134 
141  protected function ‪applySettingsToSalt(string $salt): string
142  {
143  $saltWithSettings = $salt;
144  $reqLenBase64 = $this->‪getLengthBase64FromBytes(6);
145  // Salt without setting
146  if (strlen($salt) == $reqLenBase64) {
147  // We encode the final log2 iteration count in base 64.
148  $itoa64 = $this->‪getItoa64();
149  $saltWithSettings = self::PREFIX . $itoa64[$this->options['hash_count']];
150  $saltWithSettings .= $salt;
151  }
152  return $saltWithSettings;
153  }
154 
167  protected function ‪cryptPassword(string $password, string $setting)
168  {
169  $saltedPW = null;
170  $reqLenBase64 = $this->‪getLengthBase64FromBytes(6);
171  // Retrieving settings with salt
172  $setting = substr($setting, 0, strlen(self::PREFIX) + 1 + $reqLenBase64);
173  $count_log2 = $this->‪getCountLog2($setting);
174  // Hashes may be imported from elsewhere, so we allow != HASH_COUNT
175  if ($count_log2 >= 7 && $count_log2 <= 24) {
176  $salt = substr($setting, strlen(self::PREFIX) + 1, $reqLenBase64);
177  // We must use md5() or sha1() here since they are the only cryptographic
178  // primitives always available in PHP 5. To implement our own low-level
179  // cryptographic function in PHP would result in much worse performance and
180  // consequently in lower iteration counts and hashes that are quicker to crack
181  // (by non-PHP code).
182  $count = 1 << $count_log2;
183  $hash = md5($salt . $password, true);
184  do {
185  $hash = md5($hash . $password, true);
186  } while (--$count);
187  $saltedPW = $setting . $this->‪base64Encode($hash, 16);
188  // base64Encode() of a 16 byte MD5 will always be 22 characters.
189  return strlen($saltedPW) == 34 ? $saltedPW : false;
190  }
191  return $saltedPW;
192  }
193 
200  protected function ‪getCountLog2(string $setting): int
201  {
202  return strpos($this->‪getItoa64(), $setting[strlen(self::PREFIX)]);
203  }
204 
216  protected function ‪getGeneratedSalt(): string
217  {
218  $randomBytes = GeneralUtility::makeInstance(Random::class)->generateRandomBytes(6);
219  return $this->‪base64Encode($randomBytes, 6);
220  }
221 
227  protected function ‪getItoa64(): string
228  {
229  return './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
230  }
231 
238  protected function ‪isValidSalt(string $salt): bool
239  {
240  $isValid = ($skip = false);
241  $reqLenBase64 = $this->‪getLengthBase64FromBytes(6);
242  if (strlen($salt) >= $reqLenBase64) {
243  // Salt with prefixed setting
244  if (!strncmp('$', $salt, 1)) {
245  if (!strncmp(self::PREFIX, $salt, strlen(self::PREFIX))) {
246  $isValid = true;
247  $salt = substr($salt, (int)strrpos($salt, '$') + 2);
248  } else {
249  $skip = true;
250  }
251  }
252  // Checking base64 characters
253  if (!$skip && strlen($salt) >= $reqLenBase64) {
254  if (preg_match('/^[' . preg_quote($this->‪getItoa64(), '/') . ']{' . $reqLenBase64 . ',' . $reqLenBase64 . '}$/', substr($salt, 0, $reqLenBase64))) {
255  $isValid = true;
256  }
257  }
258  }
259  return $isValid;
260  }
261 
269  protected function ‪base64Encode(string $input, int $count): string
270  {
271  ‪$output = '';
272  $i = 0;
273  $itoa64 = $this->‪getItoa64();
274  do {
275  $value = ord($input[$i++]);
276  ‪$output .= $itoa64[$value & 63];
277  if ($i < $count) {
278  $value |= ord($input[$i]) << 8;
279  }
280  ‪$output .= $itoa64[$value >> 6 & 63];
281  if ($i++ >= $count) {
282  break;
283  }
284  if ($i < $count) {
285  $value |= ord($input[$i]) << 16;
286  }
287  ‪$output .= $itoa64[$value >> 12 & 63];
288  if ($i++ >= $count) {
289  break;
290  }
291  ‪$output .= $itoa64[$value >> 18 & 63];
292  } while ($i < $count);
293  return ‪$output;
294  }
295 
303  protected function ‪getLengthBase64FromBytes(int $byteLength): int
304  {
305  // Calculates bytes in bits in base64
306  return (int)ceil($byteLength * 8 / 6);
307  }
308 }
‪TYPO3\CMS\Core\Crypto\PasswordHashing\PhpassPasswordHash\isValidSalt
‪bool isValidSalt(string $salt)
Definition: PhpassPasswordHash.php:237
‪TYPO3\CMS\Core\Crypto\PasswordHashing
Definition: AbstractArgon2PasswordHash.php:18
‪TYPO3\CMS\Core\Crypto\PasswordHashing\PhpassPasswordHash\checkPassword
‪bool checkPassword(string $plainPW, string $saltedHashPW)
Definition: PhpassPasswordHash.php:73
‪TYPO3\CMS\Core\Crypto\PasswordHashing\PhpassPasswordHash\applySettingsToSalt
‪string applySettingsToSalt(string $salt)
Definition: PhpassPasswordHash.php:140
‪TYPO3\CMS\Core\Crypto\PasswordHashing\PhpassPasswordHash\getItoa64
‪string getItoa64()
Definition: PhpassPasswordHash.php:226
‪TYPO3\CMS\Core\Crypto\PasswordHashing\PhpassPasswordHash\isValidSaltedPW
‪bool isValidSaltedPW(string $saltedPW)
Definition: PhpassPasswordHash.php:125
‪TYPO3\CMS\Core\Crypto\PasswordHashing\PhpassPasswordHash\getLengthBase64FromBytes
‪int getLengthBase64FromBytes(int $byteLength)
Definition: PhpassPasswordHash.php:302
‪TYPO3\CMS\Core\Crypto\PasswordHashing\PhpassPasswordHash\cryptPassword
‪mixed cryptPassword(string $password, string $setting)
Definition: PhpassPasswordHash.php:166
‪TYPO3\CMS\Core\Crypto\PasswordHashing\PhpassPasswordHash\getCountLog2
‪int getCountLog2(string $setting)
Definition: PhpassPasswordHash.php:199
‪TYPO3\CMS\Core\Crypto\PasswordHashing\PhpassPasswordHash\__construct
‪__construct(array $options=[])
Definition: PhpassPasswordHash.php:50
‪TYPO3\CMS\Core\Crypto\PasswordHashing\PhpassPasswordHash\$options
‪array $options
Definition: PhpassPasswordHash.php:43
‪TYPO3\CMS\Core\Crypto\PasswordHashing\PhpassPasswordHash
Definition: PhpassPasswordHash.php:35
‪$output
‪$output
Definition: annotationChecker.php:114
‪TYPO3\CMS\Core\Crypto\PasswordHashing\PhpassPasswordHash\isHashUpdateNeeded
‪bool isHashUpdateNeeded(string $passString)
Definition: PhpassPasswordHash.php:109
‪TYPO3\CMS\Core\Crypto\PasswordHashing\PhpassPasswordHash\getHashedPassword
‪getHashedPassword(string $password)
Definition: PhpassPasswordHash.php:89
‪TYPO3\CMS\Core\Crypto\PasswordHashing\PhpassPasswordHash\PREFIX
‪const PREFIX
Definition: PhpassPasswordHash.php:39
‪TYPO3\CMS\Core\Crypto\PasswordHashing\PhpassPasswordHash\getGeneratedSalt
‪string getGeneratedSalt()
Definition: PhpassPasswordHash.php:215
‪TYPO3\CMS\Core\Crypto\Random
Definition: Random.php:27
‪TYPO3\CMS\Core\Utility\GeneralUtility
Definition: GeneralUtility.php:52
‪TYPO3\CMS\Core\Crypto\PasswordHashing\PasswordHashInterface
Definition: PasswordHashInterface.php:25
‪TYPO3\CMS\Core\Crypto\PasswordHashing\PhpassPasswordHash\isAvailable
‪bool isAvailable()
Definition: PhpassPasswordHash.php:84
‪TYPO3\CMS\Core\Crypto\PasswordHashing\PhpassPasswordHash\base64Encode
‪string base64Encode(string $input, int $count)
Definition: PhpassPasswordHash.php:268