<?php
namespace XF\Authentication;
use function intval, is_string;
class Core12 extends AbstractAuth
{
protected function getDefaultOptions()
{
$config = \XF::config();
if (!empty($config['auth']))
{
return array_replace([
'algo' => PASSWORD_BCRYPT,
'options' => []
], $config['auth']);
}
else
{
return [
'algo' => PASSWORD_BCRYPT,
'options' => [
'cost' => $config['passwordIterations']
]
];
}
}
protected function getHandler()
{
return new PasswordHash(\XF::config('passwordIterations'), false);
}
public function generate($password)
{
$options = $this->getDefaultOptions();
$password = $this->truncatePassword($password);
$hash = password_hash($password, $options['algo'], $options['options']);
return [
'hash' => $hash
];
}
public function authenticate($userId, $password)
{
if (!is_string($password) || $password === '' || empty($this->data))
{
return false;
}
$password = $this->truncatePassword($password);
if ($this->isLegacyHash())
{
return $this->getHandler()->CheckPassword($password, $this->data['hash']);
}
else
{
return password_verify($password, $this->data['hash']);
}
}
public function isUpgradable()
{
if (!empty($this->data['hash']))
{
$hash = $this->data['hash'];
$options = $this->getDefaultOptions();
if ($this->isLegacyHash())
{
$expectedIterations = min(intval($options['options']['cost']), 30);
preg_match('/^\$(P|H)\$(.)/i', $hash, $match);
$iterations = $this->getHandler()->reverseItoA64($match[2]) - 5; // 5 iterations removed in PHP 5
return $expectedIterations !== $iterations;
}
else
{
return password_needs_rehash($hash, $options['algo'], $options['options']);
}
}
return true;
}
/**
* Explicitly limits the password length to mitigate a potential algorithm-specific DoS.
* Note that algorithms may have their own internal limits applied, like bcrypt's 72 byte limit.
*
* We limit to 4K by default as that should exceed any reasonable password length.
*
* @param string $password
*
* @return string
*/
protected function truncatePassword(string $password): string
{
return substr($password, 0, 4096);
}
public function getAuthenticationName()
{
return 'XF:Core12';
}
}