Implementing Secure Password Storage in PHP with password_hash
Learn how to securely implement password hashing in PHP using password_hash, password_verify, and password_needs_rehash to ensure future-proof authentication.
09 Aug 2026, 13:48 UTC

The Problem: Avoiding Custom Encryption and Salt Management
Many developers mistakenly attempt to create secure passwords by manually concatenating salts or using outdated functions like md5() or sha1(). These methods are vulnerable to rainbow table attacks and hardware-accelerated brute-forcing. The goal is to implement a storage system where the plaintext password is never stored and the hashing algorithm can evolve without breaking existing user accounts.
Prerequisites
- PHP 7.2 or higher (ensures support for modern algorithms).
- A database table with a
password_hashcolumn. This column must be aVARCHARorTEXTtype with a length of at least 255 characters to accommodate future algorithm changes.
Step 1: Generating the Secure Hash
When a user creates an account or updates their password, use password_hash(). This function automatically generates a secure, random salt and includes it within the resulting string.
// Run this in your registration or password-update logic
$plaintextPassword = $_POST['password'];
// PASSWORD_DEFAULT uses the strongest current algorithm (currently bcrypt)
$hashedPassword = password_hash($plaintextPassword, PASSWORD_DEFAULT);
// Store $hashedPassword directly in the database
// Example: UPDATE users SET password_hash = ? WHERE id = ?
Risk: Never hash the password a second time before storing it. password_hash() handles the salt internally; adding your own salt or hashing the result again increases complexity without adding security and may lead to truncation errors.
Step 2: Verifying Passwords During Login
To authenticate a user, retrieve the stored hash from the database and compare it against the provided plaintext password using password_verify().
// Run this in your login authentication logic
$userInput = $_POST['password'];
$storedHash = $userFromDb['password_hash']; // Retrieved from database
if (password_verify($userInput, $storedHash)) {
echo "Login successful";
// Proceed to session management
} else {
echo "Invalid password";
// Handle authentication failure
}
Step 3: Managing Algorithm Upgrades (Rehashing)
The PASSWORD_DEFAULT constant allows PHP to update the hashing algorithm (e.g., from bcrypt to Argon2id) in future versions. If the underlying algorithm changes, existing hashes remain valid for verification, but they should be updated to the new standard the next time the user logs in.
// After a successful password_verify() call
if (password_needs_rehash($storedHash, PASSWORD_DEFAULT)) {
// The hash was created with an older algorithm or different cost
$newHash = password_hash($userInput, PASSWORD_DEFAULT);
// Update the database with the new hash
// Example: UPDATE users SET password_hash = ? WHERE id = ?
echo "Password hash updated to latest standard.";
}
Comparison: Manual Salting vs. password_hash
| Feature | Manual Salting (Legacy) | password_hash() |
|---|---|---|
| Salt Storage | Requires separate DB column | Embedded in the hash string |
| Algorithm Agility | Hardcoded; requires migration scripts | Automatic via PASSWORD_DEFAULT |
| Complexity | High (developer manages salt/iv) | Low (handled by PHP core) |
Verification and Testing
To verify the implementation, run a standalone PHP script via CLI with the following checks:
- Positive Match: Generate a hash from a string and ensure
password_verify()returnstruefor that same string. - Negative Match: Ensure
password_verify()returnsfalsewhen provided with a slightly modified version of the password. - Rehash Trigger: Use a specific algorithm (like
PASSWORD_BCRYPT) to create a hash, then check ifpassword_needs_rehash()returnstruewhen compared againstPASSWORD_DEFAULT(if the default has changed).
Recovery and Limitations
If a user cannot log in despite providing the correct password, or if the database hash has been corrupted (e.g., truncated due to a column length limit), the hash cannot be "repaired" because the plaintext is gone. The only recovery path is to trigger a password reset flow via email to allow the user to establish a new hash.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.