‪TYPO3CMS  9.5
PhpassPasswordHash.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 
34 {
36 
40  private ‪$deprecatedPublicMethods = [
41  'isValidSalt' => 'Using PhpassPasswordHash::isValidSalt() is deprecated and will not be possible anymore in TYPO3 v10.0.',
42  'base64Encode' => 'Using PhpassPasswordHash::base64Encode() is deprecated and will not be possible anymore in TYPO3 v10.0.',
43  ];
44 
48  protected const ‪PREFIX = '$P$';
49 
53  protected ‪$options = [
54  'hash_count' => 14
55  ];
56 
63  const ‪ITOA64 = './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
64 
70  const ‪HASH_COUNT = 14;
71 
78  const ‪MAX_HASH_COUNT = 24;
79 
86  const ‪MIN_HASH_COUNT = 7;
87 
93  public function ‪__construct(array ‪$options = [])
94  {
95  $newOptions = ‪$this->options;
96  if (isset(‪$options['hash_count'])) {
97  if ((int)‪$options['hash_count'] < 7 || (int)‪$options['hash_count'] > 24) {
98  throw new \InvalidArgumentException(
99  'hash_count must not be lower than 7 or bigger than 24',
100  1533940454
101  );
102  }
103  $newOptions['hash_count'] = (int)‪$options['hash_count'];
104  }
105  $this->options = $newOptions;
106  }
107 
116  public function ‪checkPassword(string $plainPW, string $saltedHashPW): bool
117  {
118  $hash = $this->‪cryptPassword($plainPW, $saltedHashPW);
119  return $hash && hash_equals($hash, $saltedHashPW);
120  }
121 
127  public function ‪isAvailable(): bool
128  {
129  return true;
130  }
131 
139  public function ‪getHashedPassword(string $password, string $salt = null)
140  {
141  if ($salt !== null) {
142  trigger_error(static::class . ': using a custom salt is deprecated.', E_USER_DEPRECATED);
143  }
144  $saltedPW = null;
145  if (!empty($password)) {
146  if (empty($salt) || !$this->‪isValidSalt($salt)) {
147  $salt = $this->‪getGeneratedSalt();
148  }
149  $saltedPW = $this->‪cryptPassword($password, $this->‪applySettingsToSalt($salt));
150  }
151  return $saltedPW;
152  }
153 
164  public function ‪isHashUpdateNeeded(string $passString): bool
165  {
166  // Check whether this was an updated password.
167  if (strncmp($passString, '$P$', 3) || strlen($passString) != 34) {
168  return true;
169  }
170  // Check whether the iteration count used differs from the standard number.
171  return $this->‪getCountLog2($passString) < $this->options['hash_count'];
172  }
173 
180  public function ‪isValidSaltedPW(string $saltedPW): bool
181  {
182  $isValid = !strncmp(self::PREFIX, $saltedPW, strlen(self::PREFIX));
183  if ($isValid) {
184  $isValid = $this->‪isValidSalt($saltedPW);
185  }
186  return $isValid;
187  }
188 
195  protected function ‪applySettingsToSalt(string $salt): string
196  {
197  $saltWithSettings = $salt;
198  $reqLenBase64 = $this->‪getLengthBase64FromBytes(6);
199  // Salt without setting
200  if (strlen($salt) == $reqLenBase64) {
201  // We encode the final log2 iteration count in base 64.
202  $itoa64 = $this->‪getItoa64();
203  $saltWithSettings = self::PREFIX . $itoa64[$this->options['hash_count']];
204  $saltWithSettings .= $salt;
205  }
206  return $saltWithSettings;
207  }
208 
221  protected function ‪cryptPassword(string $password, string $setting)
222  {
223  $saltedPW = null;
224  $reqLenBase64 = $this->‪getLengthBase64FromBytes(6);
225  // Retrieving settings with salt
226  $setting = substr($setting, 0, strlen(self::PREFIX) + 1 + $reqLenBase64);
227  $count_log2 = $this->‪getCountLog2($setting);
228  // Hashes may be imported from elsewhere, so we allow != HASH_COUNT
229  if ($count_log2 >= 7 && $count_log2 <= 24) {
230  $salt = substr($setting, strlen(self::PREFIX) + 1, $reqLenBase64);
231  // We must use md5() or sha1() here since they are the only cryptographic
232  // primitives always available in PHP 5. To implement our own low-level
233  // cryptographic function in PHP would result in much worse performance and
234  // consequently in lower iteration counts and hashes that are quicker to crack
235  // (by non-PHP code).
236  $count = 1 << $count_log2;
237  $hash = md5($salt . $password, true);
238  do {
239  $hash = md5($hash . $password, true);
240  } while (--$count);
241  $saltedPW = $setting . $this->‪base64Encode($hash, 16);
242  // base64Encode() of a 16 byte MD5 will always be 22 characters.
243  return strlen($saltedPW) == 34 ? $saltedPW : false;
244  }
245  return $saltedPW;
246  }
247 
254  protected function ‪getCountLog2(string $setting): int
255  {
256  return strpos($this->‪getItoa64(), $setting[strlen(self::PREFIX)]);
257  }
258 
270  protected function ‪getGeneratedSalt(): string
271  {
272  $randomBytes = GeneralUtility::makeInstance(Random::class)->generateRandomBytes(6);
273  return $this->‪base64Encode($randomBytes, 6);
274  }
275 
281  protected function ‪getItoa64(): string
282  {
283  return './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
284  }
285 
292  protected function ‪isValidSalt(string $salt): bool
293  {
294  $isValid = ($skip = false);
295  $reqLenBase64 = $this->‪getLengthBase64FromBytes(6);
296  if (strlen($salt) >= $reqLenBase64) {
297  // Salt with prefixed setting
298  if (!strncmp('$', $salt, 1)) {
299  if (!strncmp(self::PREFIX, $salt, strlen(self::PREFIX))) {
300  $isValid = true;
301  $salt = substr($salt, strrpos($salt, '$') + 2);
302  } else {
303  $skip = true;
304  }
305  }
306  // Checking base64 characters
307  if (!$skip && strlen($salt) >= $reqLenBase64) {
308  if (preg_match('/^[' . preg_quote($this->‪getItoa64(), '/') . ']{' . $reqLenBase64 . ',' . $reqLenBase64 . '}$/', substr($salt, 0, $reqLenBase64))) {
309  $isValid = true;
310  }
311  }
312  }
313  return $isValid;
314  }
315 
323  protected function ‪base64Encode(string $input, int $count): string
324  {
325  ‪$output = '';
326  $i = 0;
327  $itoa64 = $this->‪getItoa64();
328  do {
329  $value = ord($input[$i++]);
330  ‪$output .= $itoa64[$value & 63];
331  if ($i < $count) {
332  $value |= ord($input[$i]) << 8;
333  }
334  ‪$output .= $itoa64[$value >> 6 & 63];
335  if ($i++ >= $count) {
336  break;
337  }
338  if ($i < $count) {
339  $value |= ord($input[$i]) << 16;
340  }
341  ‪$output .= $itoa64[$value >> 12 & 63];
342  if ($i++ >= $count) {
343  break;
344  }
345  ‪$output .= $itoa64[$value >> 18 & 63];
346  } while ($i < $count);
347  return ‪$output;
348  }
349 
357  protected function ‪getLengthBase64FromBytes(int $byteLength): int
358  {
359  // Calculates bytes in bits in base64
360  return (int)ceil($byteLength * 8 / 6);
361  }
362 
369  public function ‪getHashCount(): int
370  {
371  trigger_error('This method will be removed in TYPO3 v10.0.', E_USER_DEPRECATED);
372  return $this->options['hash_count'];
373  }
374 
381  public function ‪getMaxHashCount(): int
382  {
383  trigger_error('This method will be removed in TYPO3 v10.0.', E_USER_DEPRECATED);
384  return 24;
385  }
386 
393  public function ‪getMinHashCount(): int
394  {
395  trigger_error('This method will be removed in TYPO3 v10.0.', E_USER_DEPRECATED);
396  return 7;
397  }
398 
405  public function ‪getSaltLength(): int
406  {
407  trigger_error('This method will be removed in TYPO3 v10.0.', E_USER_DEPRECATED);
408  return 6;
409  }
410 
417  public function ‪getSetting(): string
418  {
419  trigger_error('This method will be removed in TYPO3 v10.0.', E_USER_DEPRECATED);
420  return ‪self::PREFIX;
421  }
422 
429  public function ‪setHashCount(int $hashCount = null)
430  {
431  trigger_error('This method will be removed in TYPO3 v10.0.', E_USER_DEPRECATED);
432  if ($hashCount >= 7 && $hashCount <= 24) {
433  $this->options['hash_count'] = $hashCount;
434  }
435  }
436 
443  public function ‪setMaxHashCount(int $maxHashCount = null)
444  {
445  trigger_error('This method will be removed in TYPO3 v10.0.', E_USER_DEPRECATED);
446  // Empty, max hash count is hard coded to 24
447  }
448 
455  public function ‪setMinHashCount(int $minHashCount = null)
456  {
457  trigger_error('This method will be removed in TYPO3 v10.0.', E_USER_DEPRECATED);
458  // Empty, max hash count is hard coded to 7
459  }
460 }
‪TYPO3\CMS\Core\Crypto\PasswordHashing\PhpassPasswordHash\$deprecatedPublicMethods
‪array $deprecatedPublicMethods
Definition: PhpassPasswordHash.php:39
‪TYPO3\CMS\Core\Crypto\PasswordHashing\PhpassPasswordHash\getHashCount
‪int getHashCount()
Definition: PhpassPasswordHash.php:367
‪TYPO3\CMS\Core\Crypto\PasswordHashing\PhpassPasswordHash\setHashCount
‪setHashCount(int $hashCount=null)
Definition: PhpassPasswordHash.php:427
‪TYPO3\CMS\Core\Crypto\PasswordHashing\PhpassPasswordHash\isValidSalt
‪bool isValidSalt(string $salt)
Definition: PhpassPasswordHash.php:290
‪TYPO3\CMS\Core\Crypto\PasswordHashing\PhpassPasswordHash\getSetting
‪string getSetting()
Definition: PhpassPasswordHash.php:415
‪TYPO3\CMS\Core\Crypto\PasswordHashing\PhpassPasswordHash\setMaxHashCount
‪setMaxHashCount(int $maxHashCount=null)
Definition: PhpassPasswordHash.php:441
‪TYPO3\CMS\Core\Crypto\PasswordHashing
Definition: AbstractComposedSalt.php:3
‪TYPO3\CMS\Core\Crypto\PasswordHashing\PhpassPasswordHash\checkPassword
‪bool checkPassword(string $plainPW, string $saltedHashPW)
Definition: PhpassPasswordHash.php:114
‪TYPO3\CMS\Core\Crypto\PasswordHashing\PhpassPasswordHash\applySettingsToSalt
‪string applySettingsToSalt(string $salt)
Definition: PhpassPasswordHash.php:193
‪TYPO3\CMS\Core\Crypto\PasswordHashing\PhpassPasswordHash\getItoa64
‪string getItoa64()
Definition: PhpassPasswordHash.php:279
‪TYPO3\CMS\Core\Crypto\PasswordHashing\PhpassPasswordHash\isValidSaltedPW
‪bool isValidSaltedPW(string $saltedPW)
Definition: PhpassPasswordHash.php:178
‪TYPO3\CMS\Core\Crypto\PasswordHashing\PhpassPasswordHash\setMinHashCount
‪setMinHashCount(int $minHashCount=null)
Definition: PhpassPasswordHash.php:453
‪TYPO3\CMS\Core\Crypto\PasswordHashing\PhpassPasswordHash\getHashedPassword
‪string null getHashedPassword(string $password, string $salt=null)
Definition: PhpassPasswordHash.php:137
‪TYPO3\CMS\Core\Crypto\PasswordHashing\PhpassPasswordHash\getLengthBase64FromBytes
‪int getLengthBase64FromBytes(int $byteLength)
Definition: PhpassPasswordHash.php:355
‪TYPO3\CMS\Core\Crypto\PasswordHashing\PhpassPasswordHash\HASH_COUNT
‪const HASH_COUNT
Definition: PhpassPasswordHash.php:68
‪TYPO3\CMS\Core\Crypto\PasswordHashing\PhpassPasswordHash\cryptPassword
‪mixed cryptPassword(string $password, string $setting)
Definition: PhpassPasswordHash.php:219
‪TYPO3\CMS\Core\Crypto\PasswordHashing\PhpassPasswordHash\getCountLog2
‪int getCountLog2(string $setting)
Definition: PhpassPasswordHash.php:252
‪TYPO3\CMS\Core\Crypto\PasswordHashing\PhpassPasswordHash\getMinHashCount
‪int getMinHashCount()
Definition: PhpassPasswordHash.php:391
‪TYPO3\CMS\Core\Crypto\PasswordHashing\PhpassPasswordHash\MAX_HASH_COUNT
‪const MAX_HASH_COUNT
Definition: PhpassPasswordHash.php:76
‪TYPO3\CMS\Core\Crypto\PasswordHashing\PhpassPasswordHash\ITOA64
‪const ITOA64
Definition: PhpassPasswordHash.php:61
‪TYPO3\CMS\Core\Crypto\PasswordHashing\PhpassPasswordHash\MIN_HASH_COUNT
‪const MIN_HASH_COUNT
Definition: PhpassPasswordHash.php:84
‪TYPO3\CMS\Core\Crypto\PasswordHashing\PhpassPasswordHash\getSaltLength
‪int getSaltLength()
Definition: PhpassPasswordHash.php:403
‪TYPO3\CMS\Core\Crypto\PasswordHashing\PhpassPasswordHash\getMaxHashCount
‪int getMaxHashCount()
Definition: PhpassPasswordHash.php:379
‪TYPO3\CMS\Core\Crypto\PasswordHashing\PhpassPasswordHash\__construct
‪__construct(array $options=[])
Definition: PhpassPasswordHash.php:91
‪TYPO3\CMS\Core\Crypto\PasswordHashing\PhpassPasswordHash\$options
‪array $options
Definition: PhpassPasswordHash.php:51
‪TYPO3\CMS\Core\Compatibility\PublicMethodDeprecationTrait
Definition: PublicMethodDeprecationTrait.php:68
‪TYPO3\CMS\Core\Crypto\PasswordHashing\PhpassPasswordHash
Definition: PhpassPasswordHash.php:34
‪$output
‪$output
Definition: annotationChecker.php:113
‪TYPO3\CMS\Core\Crypto\PasswordHashing\PhpassPasswordHash\isHashUpdateNeeded
‪bool isHashUpdateNeeded(string $passString)
Definition: PhpassPasswordHash.php:162
‪TYPO3\CMS\Core\Crypto\PasswordHashing\PhpassPasswordHash\PREFIX
‪const PREFIX
Definition: PhpassPasswordHash.php:47
‪TYPO3\CMS\Core\Crypto\PasswordHashing\PhpassPasswordHash\getGeneratedSalt
‪string getGeneratedSalt()
Definition: PhpassPasswordHash.php:268
‪TYPO3\CMS\Core\Crypto\Random
Definition: Random.php:22
‪TYPO3\CMS\Core\Utility\GeneralUtility
Definition: GeneralUtility.php:45
‪TYPO3\CMS\Core\Crypto\PasswordHashing\PasswordHashInterface
Definition: PasswordHashInterface.php:23
‪TYPO3\CMS\Core\Crypto\PasswordHashing\PhpassPasswordHash\isAvailable
‪bool isAvailable()
Definition: PhpassPasswordHash.php:125
‪TYPO3\CMS\Core\Crypto\PasswordHashing\PhpassPasswordHash\base64Encode
‪string base64Encode(string $input, int $count)
Definition: PhpassPasswordHash.php:321