pepper = $pepper; $this->hashAlgoLegacy = explode(',', $hashAlgoLegacy); $this->newHashAlgo = $newHashAlgo; $this->securityPattern = explode(',', $securityPattern); } /** * Utilise l'algo legacy */ public function hash(string $plainPassword, string $salt = null): string { if ($this->isPasswordTooLong($plainPassword)) { throw new InvalidPasswordException(); } $completedPlainPassword = $this->getPasswordToHash($plainPassword, $salt); if ($this->isObsoleteAlgo($this->newHashAlgo)) { throw new InvalidSQLLoginAlgoException(); } return password_hash($completedPlainPassword, $this->getValidALgo($this->newHashAlgo)); } public function verify(string $hashedPassword, string $plainPassword, string $salt = null): bool { if ('' === $plainPassword || $this->isPasswordTooLong($plainPassword)) { return false; } $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)) { return true; } else { throw new InvalidSQLPasswordException(); } } public function needsRehash(string $hashedPassword): bool { // 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)); } return false; } /** * @param mixed $algo * * @return InvalidSQLLoginAlgoException|string */ 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(); } } $completedArray = []; foreach ($this->securityPattern as $term) { $completedArray[] = $arrayRef[$term]; } $completedPlainPassword = implode($completedArray); return $completedPlainPassword; } }