<?php namespace App\Security\Hasher; use App\SQLLogin\Exception\InvalidSQLLoginConfigurationException; 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; public const PASSWORD_PATTERN = 'password'; public const SALT_PATTERN = 'salt'; public const PEPPER_PATTERN = 'pepper'; protected ?string $pepper; protected array $hashAlgoLegacy; protected array $securityPattern; public function __construct(?string $pepper, array $hashAlgoLegacy, array $securityPattern) { $this->pepper = $pepper; $this->hashAlgoLegacy = $hashAlgoLegacy; $this->securityPattern = $securityPattern; } /** * Pas utilisé, mais on doit le garder pour le implements */ public function hash(string $plainPassword, string $salt = null): string { if ($this->isPasswordTooLong($plainPassword)) { throw new InvalidPasswordException(); } return hash($plainPassword.$salt, $this->hashAlgoLegacy[0]); } public function verify(string $hashedPassword, string $plainPassword, string $salt = null): bool { if ('' === $plainPassword || $this->isPasswordTooLong($plainPassword)) { return false; } $completedPassword = $this->getPasswordToHash($plainPassword, $salt); foreach ($this->hashAlgoLegacy as $algo) { if ('ssha' === $algo && $this->compareSsha($hashedPassword, $completedPassword)) { return true; } if ($this->isObsoleteAlgo($algo)) { if (hash_equals(hash($algo, $completedPassword), $hashedPassword)) { return true; } } else { if (password_verify($completedPassword, $hashedPassword)) { return true; } } } throw new InvalidSQLPasswordException(); } public function needsRehash(string $hashedPassword): bool { return false; } 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 (!isset($arrayRef[$term])) { throw new InvalidSQLLoginConfigurationException(); } } $completedPlainPassword = ''; foreach ($this->securityPattern as $term) { $completedPlainPassword .= $arrayRef[$term]; } return $completedPlainPassword; } protected function compareSsha($hashPassword, $plainPassword) { $base_64_hash_with_salt = substr($hashPassword, 6); $hash_with_salt = base64_decode($base_64_hash_with_salt); $hash = substr($hash_with_salt, 0, 20); $salt = substr($hash_with_salt, 20); // hash given password $hash_given = sha1($plainPassword.$salt, true); return $hash == $hash_given; } }