SyntaxStudy
Sign Up
Web Security Secure Password Storage with bcrypt and Argon2
Web Security Beginner 1 min read

Secure Password Storage with bcrypt and Argon2

Storing passwords as plain text or using fast cryptographic hashes like MD5 or SHA-256 is catastrophically insecure. Fast hashes allow attackers to try billions of password combinations per second with commodity GPUs after stealing a database. Password hashing algorithms designed for authentication — bcrypt, scrypt, and Argon2 — are intentionally slow, with a configurable cost factor that makes brute-force attacks computationally infeasible even with specialised hardware. bcrypt incorporates a random salt into the hash, so two users with the same password produce different hashes. Its cost factor (work factor) doubles the computation time for every increment — a cost of 12 takes roughly twice as long as 11. OWASP recommends a minimum cost of 10, targeting 100–300 ms per hash on the server. Argon2id — winner of the Password Hashing Competition — is the modern recommendation for new applications, offering configurable memory cost, parallelism, and iterations, making it resistant to GPU and ASIC attacks. Never implement password hashing yourself. Use the platform's standard library: PHP `password_hash()` / `password_verify()` with `PASSWORD_BCRYPT` or `PASSWORD_ARGON2ID`, Python's `passlib` or Django's built-in hasher, Node's `bcrypt` or `argon2` packages. Periodically rehash passwords on login to upgrade old hashes when cost factor recommendations increase — check with `password_needs_rehash()` in PHP.
Example
<?php
// Secure password hashing with PHP password_hash()

// --- REGISTRATION: hash the password before storing ---
$plainPassword = $_POST['password']; // e.g., "correct-horse-battery-staple"

// Argon2id (preferred for new applications, PHP 7.3+)
$hashedArgon2 = password_hash($plainPassword, PASSWORD_ARGON2ID, [
    'memory_cost' => 65536,  // 64 MB
    'time_cost'   => 4,      // 4 iterations
    'threads'     => 2,
]);

// bcrypt (widely supported, excellent default)
$hashedBcrypt = password_hash($plainPassword, PASSWORD_BCRYPT, ['cost' => 12]);

// Store $hashedArgon2 or $hashedBcrypt in the database — never $plainPassword

// --- LOGIN: verify and rehash if needed ---
$storedHash = fetchHashFromDb($username); // from database

if (password_verify($plainPassword, $storedHash)) {
    // Authentication succeeded

    // Upgrade hash if algorithm or cost factor changed
    if (password_needs_rehash($storedHash, PASSWORD_ARGON2ID, [
        'memory_cost' => 65536,
        'time_cost'   => 4,
        'threads'     => 2,
    ])) {
        $newHash = password_hash($plainPassword, PASSWORD_ARGON2ID, [
            'memory_cost' => 65536,
            'time_cost'   => 4,
            'threads'     => 2,
        ]);
        updateHashInDb($username, $newHash); // transparent upgrade
    }

    startUserSession($username);
} else {
    // Use a generic error — never reveal whether username or password was wrong
    throw new AuthenticationException('Invalid credentials.');
}