2022-12-14 16:38:46 +01:00
< ? php
namespace App\Security\Hasher ;
2022-12-16 15:00:14 +01:00
use App\SQLLogin\Exception\InvalidSQLLoginAlgoException ;
use App\SQLLogin\Exception\InvalidSQLLoginConfigurationException ;
2022-12-14 16:38:46 +01:00
use App\SQLLogin\Exception\InvalidSQLPasswordException ;
use Symfony\Component\PasswordHasher\Exception\InvalidPasswordException ;
use Symfony\Component\PasswordHasher\Hasher\CheckPasswordLengthTrait ;
use Symfony\Component\PasswordHasher\LegacyPasswordHasherInterface ;
class PasswordEncoder implements LegacyPasswordHasherInterface
{
use CheckPasswordLengthTrait ;
2022-12-16 15:00:14 +01:00
public const PASSWORD_PATTERN = 'password' ;
public const SALT_PATTERN = 'salt' ;
public const PEPPER_PATTERN = 'pepper' ;
2022-12-14 16:38:46 +01:00
protected ? string $pepper ;
2022-12-16 15:00:14 +01:00
protected array $hashAlgoLegacy ;
protected ? string $newHashAlgo ;
protected array $securityPattern ;
2022-12-14 16:38:46 +01:00
2022-12-16 15:00:14 +01:00
public function __construct ( ? string $pepper , string $hashAlgoLegacy , ? string $newHashAlgo , string $securityPattern )
2022-12-14 16:38:46 +01:00
{
$this -> pepper = $pepper ;
2022-12-16 15:00:14 +01:00
$this -> hashAlgoLegacy = explode ( ',' , $hashAlgoLegacy );
$this -> newHashAlgo = $newHashAlgo ;
$this -> securityPattern = explode ( ',' , $securityPattern );
2022-12-14 16:38:46 +01:00
}
2022-12-16 15:00:14 +01:00
/**
* Utilise l ' algo legacy
*/
2022-12-14 16:38:46 +01:00
public function hash ( string $plainPassword , string $salt = null ) : string
{
if ( $this -> isPasswordTooLong ( $plainPassword )) {
throw new InvalidPasswordException ();
}
2022-12-16 15:00:14 +01:00
$completedPlainPassword = $this -> getPasswordToHash ( $plainPassword , $salt );
if ( $this -> isObsoleteAlgo ( $this -> newHashAlgo )) {
throw new InvalidSQLLoginAlgoException ();
}
2022-12-14 16:38:46 +01:00
2022-12-16 15:00:14 +01:00
return password_hash ( $completedPlainPassword , $this -> getValidALgo ( $this -> newHashAlgo ));
2022-12-14 16:38:46 +01:00
}
public function verify ( string $hashedPassword , string $plainPassword , string $salt = null ) : bool
{
if ( '' === $plainPassword || $this -> isPasswordTooLong ( $plainPassword )) {
return false ;
}
2022-12-16 15:00:14 +01:00
$isNewest = password_get_info ( $hashedPassword )[ 'algo' ] === $this -> newHashAlgo ;
$completedPassword = $this -> getPasswordToHash ( $plainPassword , $salt );
if ( $isNewest ) {
if ( password_verify ( $completedPassword , $hashedPassword )) {
return true ;
} else {
throw new InvalidSQLPasswordException ();
}
}
// Si le mot de passe a besoin d'un rehash ou qu'il n'y a pas de nouvelle méthode indiquée, on sait qu'il faut utiliser l'une des méthodes legacy pour vérifier le mot de passe
if ( $this -> needsRehash ( $hashedPassword ) || empty ( $this -> newHashAlgo )) {
foreach ( $this -> hashAlgoLegacy as $algo ) {
if ( $this -> isObsoleteAlgo ( $algo )) {
if ( hash_equals ( hash ( $algo , $completedPassword ), $hashedPassword )) {
return true ;
}
} else {
if ( password_verify ( $completedPassword , $hashedPassword )) {
return true ;
}
}
// On vérifie si la méthode legacy est obsolète, si oui on ne peut pas utiliser password_verify, on doit hasher et comparer
}
// si on on n'a pas encore retourné de résultat, le mot de passe doit être incorrect
throw new InvalidSQLPasswordException ();
}
// Si on n'est pas rentré dans les conditions précedéntes, on peut utiliser password_verify
if ( password_verify ( $completedPassword , $hashedPassword )) {
2022-12-14 16:38:46 +01:00
return true ;
} else {
throw new InvalidSQLPasswordException ();
}
}
public function needsRehash ( string $hashedPassword ) : bool
{
2022-12-16 15:00:14 +01:00
// Il y a besoin de tester si on veut mettre à jour le hashage de mot de passe uniquement si le nouveau algo de hashage est indiqué et qu'il est moderne (BCRYPT, ARGON, ...)
if ( ! empty ( $this -> newHashAlgo ) && ! $this -> isObsoleteAlgo ( $this -> newHashAlgo )) {
return password_needs_rehash ( $hashedPassword , $this -> getValidAlgo ( $this -> newHashAlgo ));
}
2022-12-14 16:38:46 +01:00
return false ;
}
2022-12-16 15:00:14 +01:00
/**
* @ param mixed $algo
*
2022-12-16 15:39:42 +01:00
* @ return string
2022-12-16 15:00:14 +01:00
*/
public function getValidAlgo ( $algo )
{
if ( $algo ) {
if ( in_array ( $algo , hash_algos ())) {
return $algo ;
}
$informalAlgos = [
'default' => PASSWORD_DEFAULT ,
'bcrypt' => PASSWORD_BCRYPT ,
'argon2i' => PASSWORD_ARGON2I ,
'argon2id' => PASSWORD_ARGON2ID ,
];
if ( in_array ( $algo , array_keys ( $informalAlgos ))) {
return $informalAlgos [ $algo ];
}
if ( in_array ( $algo , password_algos ())) {
return $algo ;
}
throw new InvalidSQLLoginAlgoException ( 'Invalid Algorythme' );
}
}
public function isObsoleteAlgo ( $algo ) : bool
{
return in_array ( $algo , hash_algos ());
}
/**
* Retourne la string à hasher en fonction du pattern indiqué
*
* @ param mixed $plainTextPassword
* @ param mixed $salt
*
* @ return string
*/
protected function getPasswordToHash ( $plainTextPassword , $salt )
{
$arrayRef = [
self :: PASSWORD_PATTERN => $plainTextPassword ,
self :: SALT_PATTERN => $salt ,
self :: PEPPER_PATTERN => $this -> pepper ,
];
foreach ( $this -> securityPattern as $term ) {
if ( self :: PEPPER_PATTERN !== $term && self :: PASSWORD_PATTERN !== $term && self :: SALT_PATTERN !== $term ) {
throw new InvalidSQLLoginConfigurationException ();
}
}
2022-12-16 15:39:42 +01:00
$completedPlainPassword = '' ;
2022-12-16 15:00:14 +01:00
foreach ( $this -> securityPattern as $term ) {
2022-12-16 15:39:42 +01:00
$completedPlainPassword .= $arrayRef [ $term ];
2022-12-16 15:00:14 +01:00
}
return $completedPlainPassword ;
}
2022-12-14 16:38:46 +01:00
}