TYPO3 CMS  TYPO3_8-7
Pbkdf2Salt.php
Go to the documentation of this file.
1 <?php
3 
4 /*
5  * This file is part of the TYPO3 CMS project.
6  *
7  * It is free software; you can redistribute it and/or modify it under
8  * the terms of the GNU General Public License, either version 2
9  * of the License, or any later version.
10  *
11  * For the full copyright and license information, please read the
12  * LICENSE.txt file that was distributed with this source code.
13  *
14  * The TYPO3 project - inspiring people to share!
15  */
16 
19 
24 class Pbkdf2Salt extends AbstractSalt implements SaltInterface
25 {
30  const ITOA64 = './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
31 
35  const HASH_COUNT = 25000;
36 
40  const MAX_HASH_COUNT = 10000000;
41 
45  const MIN_HASH_COUNT = 1000;
46 
52  protected static $hashCount;
53 
59  protected static $maxHashCount;
60 
66  protected static $minHashCount;
67 
73  protected static $saltLengthPbkdf2 = 16;
74 
80  protected static $settingPbkdf2 = '$pbkdf2-sha256$';
81 
91  protected function applySettingsToSalt($salt)
92  {
93  $saltWithSettings = $salt;
94  // salt without setting
95  if (strlen($salt) === $this->getSaltLength()) {
96  $saltWithSettings = $this->getSetting() . sprintf('%02u', $this->getHashCount()) . '$' . $this->base64Encode($salt, $this->getSaltLength());
97  }
98  return $saltWithSettings;
99  }
100 
109  public function checkPassword($plainPW, $saltedHashPW)
110  {
111  return $this->isValidSalt($saltedHashPW) && hash_equals($this->getHashedPassword($plainPW, $saltedHashPW), $saltedHashPW);
112  }
113 
120  protected function getIterationCount($setting)
121  {
122  $iterationCount = null;
123  $setting = substr($setting, strlen($this->getSetting()));
124  $firstSplitPos = strpos($setting, '$');
125  // Hashcount existing
126  if ($firstSplitPos !== false
127  && $firstSplitPos <= strlen((string)$this->getMaxHashCount())
128  && is_numeric(substr($setting, 0, $firstSplitPos))
129  ) {
130  $iterationCount = (int)substr($setting, 0, $firstSplitPos);
131  }
132  return $iterationCount;
133  }
134 
146  protected function getGeneratedSalt()
147  {
148  return GeneralUtility::makeInstance(Random::class)->generateRandomBytes($this->getSaltLength());
149  }
150 
158  protected function getStoredSalt($salt)
159  {
160  if (!strncmp('$', $salt, 1)) {
161  if (!strncmp($this->getSetting(), $salt, strlen($this->getSetting()))) {
162  $saltParts = GeneralUtility::trimExplode('$', $salt, 4);
163  $salt = $saltParts[2];
164  }
165  }
166  return $this->base64Decode($salt);
167  }
168 
174  protected function getItoa64()
175  {
176  return self::ITOA64;
177  }
178 
186  public function getHashedPassword($password, $salt = null)
187  {
188  $saltedPW = null;
189  if ($password !== '') {
190  if (empty($salt) || !$this->isValidSalt($salt)) {
191  $salt = $this->getGeneratedSalt();
192  } else {
193  $this->setHashCount($this->getIterationCount($salt));
194  $salt = $this->getStoredSalt($salt);
195  }
196  $hash = hash_pbkdf2('sha256', $password, $salt, $this->getHashCount(), 0, true);
197  $saltedPW = $this->applySettingsToSalt($salt) . '$' . $this->base64Encode($hash, strlen($hash));
198  }
199  return $saltedPW;
200  }
201 
210  public function getHashCount()
211  {
212  return isset(self::$hashCount) ? self::$hashCount : self::HASH_COUNT;
213  }
214 
223  public function getMaxHashCount()
224  {
225  return isset(self::$maxHashCount) ? self::$maxHashCount : self::MAX_HASH_COUNT;
226  }
227 
233  public function isAvailable()
234  {
235  return function_exists('hash_pbkdf2');
236  }
237 
246  public function getMinHashCount()
247  {
248  return isset(self::$minHashCount) ? self::$minHashCount : self::MIN_HASH_COUNT;
249  }
250 
259  public function getSaltLength()
260  {
261  return self::$saltLengthPbkdf2;
262  }
263 
272  public function getSetting()
273  {
274  return self::$settingPbkdf2;
275  }
276 
288  public function isHashUpdateNeeded($saltedPW)
289  {
290  // Check whether this was an updated password.
291  if (strncmp($saltedPW, $this->getSetting(), strlen($this->getSetting())) || !$this->isValidSalt($saltedPW)) {
292  return true;
293  }
294  // Check whether the iteration count used differs from the standard number.
295  $iterationCount = $this->getIterationCount($saltedPW);
296  return !is_null($iterationCount) && $iterationCount < $this->getHashCount();
297  }
298 
308  public function isValidSalt($salt)
309  {
310  $isValid = ($skip = false);
311  $reqLenBase64 = $this->getLengthBase64FromBytes($this->getSaltLength());
312  if (strlen($salt) >= $reqLenBase64) {
313  // Salt with prefixed setting
314  if (!strncmp('$', $salt, 1)) {
315  if (!strncmp($this->getSetting(), $salt, strlen($this->getSetting()))) {
316  $isValid = true;
317  $salt = substr($salt, strrpos($salt, '$') + 1);
318  } else {
319  $skip = true;
320  }
321  }
322  // Checking base64 characters
323  if (!$skip && strlen($salt) >= $reqLenBase64) {
324  if (preg_match('/^[' . preg_quote($this->getItoa64(), '/') . ']{' . $reqLenBase64 . ',' . $reqLenBase64 . '}$/', substr($salt, 0, $reqLenBase64))) {
325  $isValid = true;
326  }
327  }
328  }
329  return $isValid;
330  }
331 
338  public function isValidSaltedPW($saltedPW)
339  {
340  $isValid = !strncmp($this->getSetting(), $saltedPW, strlen($this->getSetting()));
341  if ($isValid) {
342  $isValid = $this->isValidSalt($saltedPW);
343  }
344  return $isValid;
345  }
346 
355  public function setHashCount($hashCount = null)
356  {
357  self::$hashCount = !is_null($hashCount) && is_int($hashCount) && $hashCount >= $this->getMinHashCount() && $hashCount <= $this->getMaxHashCount() ? $hashCount : self::HASH_COUNT;
358  }
359 
368  public function setMaxHashCount($maxHashCount = null)
369  {
370  self::$maxHashCount = !is_null($maxHashCount) && is_int($maxHashCount) ? $maxHashCount : self::MAX_HASH_COUNT;
371  }
372 
381  public function setMinHashCount($minHashCount = null)
382  {
383  self::$minHashCount = !is_null($minHashCount) && is_int($minHashCount) ? $minHashCount : self::MIN_HASH_COUNT;
384  }
385 
394  public function base64Encode($input, $count)
395  {
396  $input = substr($input, 0, $count);
397  return rtrim(str_replace('+', '.', base64_encode($input)), " =\r\n\t\0\x0B");
398  }
399 
407  public function base64Decode($value)
408  {
409  return base64_decode(str_replace('.', '+', $value));
410  }
411 }
getHashedPassword($password, $salt=null)
Definition: Pbkdf2Salt.php:186
static trimExplode($delim, $string, $removeEmptyValues=false, $limit=0)
static makeInstance($className,... $constructorArguments)
checkPassword($plainPW, $saltedHashPW)
Definition: Pbkdf2Salt.php:109