Lines 100.00% 11 / 11
Methods 100.00% 6 / 6
Classes 100.00% 1 / 1
Covered by tests of size
Name Lines Methods CRAP
 __construct 100.00% 1 / 1 100.00% 1 / 1 1
 hash 100.00% 1 / 1 100.00% 1 / 1 1
 verify 100.00% 3 / 3 100.00% 1 / 1 2
 needsRehash 100.00% 3 / 3 100.00% 1 / 1 2
 isLegacyHash 100.00% 1 / 1 100.00% 1 / 1 1
 legacyHash 100.00% 2 / 2 100.00% 1 / 1 1
27final class PasswordHasher
28{
29    public function __construct(
30        private readonly Configuration $configuration,
31    ) {
32    }
33
34    /**
35     * Hashes a password for storage using bcrypt.
36     */
37    public function hash(#[SensitiveParameter] string $password): string
38    {
39        return password_hash($password, PASSWORD_BCRYPT);
40    }
41
42    /**
43     * Verifies a password against a stored hash, accepting both bcrypt and
44     * legacy salted SHA-256 hashes.
45     */
46    public function verify(string $login, #[SensitiveParameter] string $password, string $storedHash): bool
47    {
48        if ($this->isLegacyHash($storedHash)) {
49            return hash_equals($storedHash, $this->legacyHash($login, $password));
50        }
51
52        return password_verify($password, $storedHash);
53    }
54
55    /**
56     * Returns true when the stored hash should be upgraded to current bcrypt
57     * parameters (legacy SHA-256, or bcrypt with outdated cost).
58     */
59    public function needsRehash(string $storedHash): bool
60    {
61        if ($this->isLegacyHash($storedHash)) {
62            return true;
63        }
64
65        return password_needs_rehash($storedHash, PASSWORD_BCRYPT);
66    }
67
68    private function isLegacyHash(string $storedHash): bool
69    {
70        // password_get_info() reports algo === null for non-PHC strings,
71        // i.e. the 64-char salted SHA-256 hex hashes produced by the old scheme.
72        return password_get_info($storedHash)['algo'] === null;
73    }
74
75    private function legacyHash(string $login, #[SensitiveParameter] string $password): string
76    {
77        $salt = (string) $this->configuration->get('security.salt') . $login;
78        return hash('sha256', $password . $salt);
79    }
80}