Password Hashing and Verification in PHP


Introduction

In today's digital world, securing user data—especially passwords—is more important than ever. One of the most essential practices in web development is password hashing and verification. If you're storing passwords in your PHP application without hashing, you're putting user data and your entire application at risk.

This guide will teach you everything you need to know about secure password hashing in PHP, including how to hash passwords, how to verify them, and best practices for storing and handling credentials in a modern, secure PHP application.

What Is Password Hashing?

Password hashing is a one-way encryption process that transforms a plain-text password into a fixed-length string of characters, which is practically impossible to reverse. Unlike encryption, which can be decrypted with a key, a hash cannot be reversed—making it ideal for password storage.

Why Not Store Plain Text Passwords?

Storing passwords as plain text is dangerous because:

  • Hackers can instantly gain access to all accounts if the database is breached.
  • Passwords are often reused across multiple platforms.
  • It violates security compliance standards like GDPR, PCI-DSS, etc.

Evolution of Password Hashing in PHP

md5() and sha1() — Do Not Use

Functions like md5() or sha1() were once common, but are now considered insecure due to fast hashing speed and vulnerability to rainbow table attacks.

$insecure_hash = md5($password); // Not safe

password_hash() and password_verify() — Modern and Secure

Introduced in PHP 5.5, the password_hash() function uses bcrypt by default, offering automatic salt generation and strong security.

PHP Password Hashing with password_hash()

Syntax:

string password_hash(string $password, int $algo, array $options = [])

Supported Algorithms:

  • PASSWORD_DEFAULT — uses the current strongest algorithm (bcrypt as of PHP 8.0)
  • PASSWORD_BCRYPT — bcrypt algorithm (60 characters)
  • PASSWORD_ARGON2I and PASSWORD_ARGON2ID — added in PHP 7.2+, even stronger (memory-hard)

Example: Hash a Password Using Bcrypt

$password = "userSecurePassword123!";
$hashedPassword = password_hash($password, PASSWORD_DEFAULT);

echo $hashedPassword;

Output is a 60-character hashed string with auto-generated salt.

How Password Hashing Works Internally

  • Salting: Adds a random value to each password before hashing.
  • One-way: Cannot be reversed.
  • Slow Algorithm: Makes brute-force attacks computationally expensive.

Example Hash Output:

$2y$10$CwTycUXWue0Thq9StjUM0uJ8rkTnQo3L4eYJGZzZZ6zEHEBK5z70u
  • $2y$10$: Bcrypt version and cost factor.
  • CwTycUXWue0Thq9StjUM0u: Salt.
  • The rest: The hash.

Password Verification in PHP

Use password_verify() to compare a plain-text password with its hashed version.

Syntax:

bool password_verify(string $password, string $hash)

Example:

$enteredPassword = $_POST['password'];
$storedHash = '$2y$10$CwTycUXWue0Thq9StjUM0uJ8rkTnQo3L4eYJGZzZZ6zEHEBK5z70u';

if (password_verify($enteredPassword, $storedHash)) {
    echo "Password is correct!";
} else {
    echo "Invalid password.";
}

Safe against timing attacks and doesn't expose password data.

PHP Login System Example with Hashed Passwords

1. Register User and Hash Password

if ($_SERVER["REQUEST_METHOD"] == "POST") {
    $username = $_POST["username"];
    $password = password_hash($_POST["password"], PASSWORD_DEFAULT);

    // Store $username and $password in your database securely
}

2. Login and Verify Password

if ($_SERVER["REQUEST_METHOD"] == "POST") {
    $username = $_POST["username"];
    $enteredPassword = $_POST["password"];

    // Retrieve $storedHash from DB by $username
    $stmt = $pdo->prepare("SELECT password FROM users WHERE username = ?");
    $stmt->execute([$username]);
    $storedHash = $stmt->fetchColumn();

    if (password_verify($enteredPassword, $storedHash)) {
        echo "Login Successful!";
    } else {
        echo "Invalid Credentials.";
    }
}

Changing the Hashing Cost

You can control the cost factor (processing time). Higher = slower = more secure.

$options = ['cost' => 12];
$hashedPassword = password_hash($password, PASSWORD_BCRYPT, $options);

Default cost is 10. Higher values are more secure but can slow down login.

Rehashing Passwords

If the hashing algorithm or cost changes, use password_needs_rehash() to upgrade old hashes:

if (password_needs_rehash($storedHash, PASSWORD_DEFAULT)) {
    $newHash = password_hash($password, PASSWORD_DEFAULT);
    // Update the database
}

Password Hashing with Argon2 (PHP 7.2+)

Argon2 is currently one of the most secure algorithms.

$hash = password_hash($password, PASSWORD_ARGON2ID);

Memory-hard, secure against GPU cracking.

Securing Your PHP Password Handling Code

  • Always Use HTTPS - Ensure password transmission is encrypted using SSL/TLS.
  • Use Prepared Statements - Avoid SQL injection when handling login credentials.
$stmt = $pdo->prepare("SELECT password FROM users WHERE email = ?");
$stmt->execute([$email]);
  • Limit Login Attempts - Implement brute-force protection using IP tracking or CAPTCHAs.
  • Do Not Store Passwords in Sessions - Store only session identifiers or tokens.

Password Hashing vs Encryption

Feature Hashing Encryption
Reversible No Yes
Use Case Passwords Data at rest
Built-in PHP Support password_hash() openssl_encrypt()