password_hash()
debt(d5/e3/b3/t3)
Closest to 'specialist tool catches it' (d5). The detection_hints list semgrep and phpstan with a specific code_pattern (md5($_POST['password'] or sha1( in login/registration handlers). This means misuse (using md5/sha1 instead of password_hash) is detectable via SAST tools like semgrep, not by the compiler or a default linter pass, placing it squarely at d5.
Closest to 'simple parameterised fix' (e3). The quick_fix is a direct swap: replace md5/sha1 calls with password_hash($password, PASSWORD_ARGON2ID) and password_verify(). However, existing stored hashes need migration (rehash at next login via password_needs_rehash()), which is a small but contained refactor within the authentication component — not a single-line patch but not cross-cutting either.
Closest to 'localised tax' (b3). password_hash() applies to web and cli contexts but its burden is confined to the authentication layer — the password storage column length and the rehash-at-login logic. The rest of the codebase is unaffected. It does impose a persistent check (password_needs_rehash() at login) but this is localised to one component.
Closest to 'minor surprise' (t3). The primary misconception documented is that storing the algorithm identifier in the hash string weakens security — this is a subtle but non-catastrophic misunderstanding. The common mistakes (double-hashing, short column truncation, not calling password_needs_rehash()) are real gotchas but are well-documented and typically caught during testing or code review rather than causing silent production failures.
Also Known As
TL;DR
Explanation
password_hash($password, PASSWORD_BCRYPT) produces a 60-character string that includes the algorithm identifier, cost factor, salt, and hash — everything password_verify() needs to check a login. The salt is generated automatically from the OS CSPRNG. Never store passwords as raw MD5/SHA1; always use password_hash() and pair it with password_verify() for comparison. PASSWORD_ARGON2ID is preferred for new systems.
Common Misconception
Why It Matters
Common Mistakes
- Passing a pre-hashed password to password_hash() — double-hashing corrupts the input entropy.
- Storing the result in a column shorter than 255 characters — truncated hashes always fail verification.
- Using PASSWORD_DEFAULT without checking what algorithm it currently maps to on your PHP version.
- Not calling password_needs_rehash() at login to upgrade old hashes when algorithm or cost factors change.
Code Examples
// Pre-hashing before password_hash — entropy loss:
$hash = password_hash(md5($password), PASSWORD_BCRYPT); // md5 first = wrong
// Column too short:
$db->query('CREATE TABLE users (password VARCHAR(60))'); // Must be 255+
// Correct:
$hash = password_hash($password, PASSWORD_ARGON2ID);
// Always use password_hash() — never md5/sha1 for passwords
$hash = password_hash($plaintext, PASSWORD_BCRYPT, ['cost' => 12]);
// Or use Argon2id (recommended for new projects, PHP 7.3+)
$hash = password_hash($plaintext, PASSWORD_ARGON2ID, [
'memory_cost' => 65536, // 64 MB
'time_cost' => 4,
'threads' => 2,
]);
// Verify
if (password_verify($input, $hash)) { /* authenticated */ }
// Upgrade hash if algorithm/cost has changed
if (password_needs_rehash($hash, PASSWORD_ARGON2ID)) {
$hash = password_hash($input, PASSWORD_ARGON2ID);
// save new hash
}