0

Hashing strings: md5, sha1, hash(), password_hash / password_verify

Intermediate5 min read·php-03-010
securityinterview

Concept

Hashing in PHP serves two fundamentally different purposes that beginners often conflate: data integrity / checksums (fast, deterministic, reversible-with-rainbow-table) and password storage (deliberately slow, salted, irreversible). Using the wrong tool for the wrong job is a security vulnerability.

Fast hash functions (hash, md5, sha1, sha256) produce a fixed-length digest from arbitrary input. They are designed to be fast — which makes them useless for passwords. An attacker with a GPU can compute billions of MD5 hashes per second. Use fast hashes for: file integrity checks, cache keys, ETag headers, HMAC signatures (hash_hmac), non-secret deduplication. Never for passwords.

password_hash($password, $algorithm, $options) is the correct function for passwords. It generates a cryptographically random salt, applies a deliberately slow algorithm (bcrypt by default, Argon2id recommended), and encodes algorithm + cost + salt + hash into a single portable string. PASSWORD_DEFAULT currently maps to bcrypt; PASSWORD_ARGON2ID is the modern choice. The cost option controls iterations — higher cost = slower = harder to brute-force.

password_verify($password, $hash) is constant-time comparison — it always takes the same amount of time regardless of whether the password is correct or not, preventing timing attacks. It also handles the $2y$10$... format detection automatically.

password_needs_rehash($hash, $algorithm, $options) checks if a stored hash was created with weaker parameters — use it on every successful login to transparently upgrade old hashes.

Code Example

php
<?php
declare(strict_types=1);

// Fast hashing — for checksums, cache keys, ETags
$data = 'file contents here';
echo md5($data);              // 32-char hex — NEVER use for passwords
echo sha1($data);             // 40-char hex
echo hash('sha256', $data);   // 64-char hex
echo hash('sha512', $data);   // 128-char hex

// HMAC — message authentication with a secret key
$secretKey = 'my-api-secret';
$message   = '{"user_id":42,"action":"purchase"}';
$signature = hash_hmac('sha256', $message, $secretKey);
// Attach $signature to the request; receiver recomputes and compares

// File integrity check
$checksum = hash_file('sha256', '/path/to/file.zip');
// Compare against published checksum to verify download integrity

// ============================================================
// PASSWORD STORAGE — use only these functions
// ============================================================

// Hashing a new password
$password = 'user-secret-password';

// bcrypt (current default) — cost 12 is a good balance
$hash = password_hash($password, PASSWORD_BCRYPT, ['cost' => 12]);
// "$2y$12$..." — includes algo, cost, salt, hash in one string

// Argon2id — recommended for new systems
$hash = password_hash($password, PASSWORD_ARGON2ID, [
    'memory_cost' => 65536, // 64MB
    'time_cost'   => 4,
    'threads'     => 2,
]);

// Verifying — constant-time, handles format detection automatically
if (password_verify($password, $hash)) {
    echo "Password correct\n";

    // Upgrade hash if parameters changed (do on every successful login)
    if (password_needs_rehash($hash, PASSWORD_ARGON2ID)) {
        $newHash = password_hash($password, PASSWORD_ARGON2ID);
        // Save $newHash to database
    }
}

// Common mistake: using MD5 for passwords
$badHash = md5($password); // NEVER do this — cracked in milliseconds