PHP 7: Maintaining and Upgrading Legacy Apps · Security Hardening for PHP 7 Apps

Upgrading Weak Password Hashes

Move from md5/sha1 hashes to password_hash without forcing resets.

Old apps store md5($password) or sha1($salt . $password). You can't reverse them, but you can migrate on login: verify with the legacy algorithm, then rehash with password_hash() and store it. For inactive users, wrap the old hash immediately: store password_hash(md5_hash) so weak hashes never sit in the database, and verify with password_verify(md5($input), $stored).

<?php
function verifyAndUpgrade(PDO $pdo, array $user, string $password): bool
{
    $hash = $user['password_hash'];
    if (strpos($hash, 'legacy-md5:') === 0) {                    // wrapped legacy hash
        $ok = password_verify(md5($password), substr($hash, 11));
    } else {
        $ok = password_verify($password, $hash);
    }
    if ($ok && (strpos($hash, 'legacy-md5:') === 0 || password_needs_rehash($hash, PASSWORD_DEFAULT))) {
        $pdo->prepare('UPDATE users SET password_hash = ? WHERE id = ?')
            ->execute([password_hash($password, PASSWORD_DEFAULT), $user['id']]);
    }
    return $ok;
}
// One-off migration for all rows:
// UPDATE: password_hash = 'legacy-md5:' . password_hash(old_md5, PASSWORD_DEFAULT)

Common pitfalls

  • Leaving raw md5 hashes in the database until users log in.
  • Comparing hashes with == (type juggling with '0e...' strings).
  • Using PHP 8-only helpers like str_starts_with() in code that must still run on PHP 7.

Key points

  • Wrap weak hashes immediately
  • Upgrade to password_hash on login
  • Never compare hashes with ==

Further reading: OWASP Password Storage Cheat Sheet

Practice questions

How can you protect md5 password hashes for users who never log in again?

Answer

Wrap them: replace each md5 hash with password_hash(md5_hash) in a migration, then verify with password_verify(md5(input), stored) and rehash properly on next login.

Why is md5($a) == md5($b) dangerous in PHP?

Answer

Loose comparison treats strings like '0e123...' as numbers in scientific notation (0), so different 'magic hashes' compare equal. Use hash_equals() or password_verify().

Open in the app to mark lessons complete, take notes and track your progress.