I currently store md5($password) in my users table. I read that is bad. What is the correct way in PHP 8 to hash passwords, verify them on login and upgrade old hashes?
DEV3 Developer Forum
How do I store and verify passwords correctly in plain PHP?
Every post is reviewed first. Usually reviewed within 24 hours.
1 reply
Never use md5, sha1 or plain hash('sha256') for passwords. They are fast, so attackers can test billions of guesses per second. Use PHP's built-in password API, which handles salting and the work factor for you.
Hashing at registration
$hash = password_hash($password, PASSWORD_DEFAULT); // currently bcrypt; may change over time
// store $hash in a VARCHAR(255) column
PASSWORD_DEFAULT can change to stronger algorithms in future PHP versions, which is why the column must be at least 255 chars. You may use PASSWORD_ARGON2ID if your PHP build supports it.
Verifying at login
$stmt = $pdo->prepare('SELECT id, password_hash FROM users WHERE email = ?');
$stmt->execute([$email]);
$user = $stmt->fetch(PDO::FETCH_ASSOC);
if ($user && password_verify($password, $user['password_hash'])) {
if (password_needs_rehash($user['password_hash'], PASSWORD_DEFAULT)) {
$new = password_hash($password, PASSWORD_DEFAULT);
$pdo->prepare('UPDATE users SET password_hash = ? WHERE id = ?')->execute([$new, $user['id']]);
}
session_regenerate_id(true);
$_SESSION['user_id'] = $user['id'];
}
password_verify is timing-safe and reads the algorithm and salt from the stored hash.
Migrating old MD5 hashes
Option 1: on each successful login, check the legacy hash, then re-hash with password_hash and overwrite. Option 2 (immediately): wrap the old hash: store password_hash(md5_hash, ...) and verify password_verify(md5($input), $stored), then upgrade to a pure hash on the next login.
Pitfalls
- Do not truncate or trim passwords. bcrypt only uses the first 72 bytes, so very long passphrases are cut; pre-hashing is rarely needed with Argon2.
- Do not write your own salt, the
saltoption was removed in PHP 8. - Return the same error for unknown user and wrong password, and rate limit attempts.
- Use
random_bytes()for reset tokens, store only a hash of the token.
You can try hashes with the bcrypt generator and checker.