Article

Password Hashing Migration for Legacy PHP

Move from MD5, SHA-1, and custom salts to password_hash, password_verify, and password_needs_rehash with a gradual rehash-on-login strategy.

Legacy apps frequently store md5($password), sha1($password), or homemade salt schemes. Those hashes are fast to brute-force and incompatible with modern password APIs. PHP’s password_hash() and password_verify() are the supported replacement; bcrypt/argon2 options are selected via algorithm constants—not by rolling your own iterations.

LEGACY — MD5 login
MODERN — verify + rehash
prepare('SELECT id, password_hash FROM users WHERE email = ?');
$stmt->execute([$email]);
$row = $stmt->fetch(PDO::FETCH_ASSOC);

if (!$row || !password_verify($password, $row['password_hash'])) {
    exit('Invalid credentials');
}

if (password_needs_rehash($row['password_hash'], PASSWORD_DEFAULT)) {
    $new = password_hash($password, PASSWORD_DEFAULT);
    $upd = $pdo->prepare('UPDATE users SET password_hash = ? WHERE id = ?');
    $upd->execute([$new, $row['id']]);
}

Gradual rehash-on-login

You rarely have plaintext passwords to convert offline. Dual-verify instead: if the stored value looks like a legacy MD5/SHA-1, check with the old algorithm once; on success, write a password_hash() result and stop accepting the legacy form for that user. Widen the column to fit modern hash strings (often 255 characters).

TRANSITIONAL — dual verify sketch

Experiment with cost parameters using PHP Password Hash. Parent security guide: PHP security modernization. Manual: password_hash.

Related reading