Implementing Argon2id for Credential Storage in PHP
Learn how to implement Argon2id in PHP using password_hash to secure user credentials against database leaks and brute-force attacks.
26 May 2026, 08:26 UTC

The Problem: Database Leaks and Credential Exposure
When a database is compromised via SQL injection or a backup leak, plaintext passwords provide immediate access to all user accounts. Traditional hashing (like MD5 or SHA-1) is insufficient because modern GPUs can calculate billions of these hashes per second, making brute-force attacks trivial.
The goal is to ensure that even if an attacker gains full read access to the user table, the cost of recovering a single password is computationally prohibitive. The most effective way to achieve this in PHP is using password_hash with the Argon2id algorithm, which is memory-hard and resistant to both GPU-based cracking and side-channel attacks.
Minimal Design for Credential Storage
The smallest suitable design avoids manual salt management and custom encryption. PHP's password_hash function handles salt generation and storage automatically within the resulting hash string.
Registration Flow
During user creation, the raw password should be passed directly to the hashing function. Do not pre-hash the password with a fast algorithm like SHA-256, as this can introduce collisions or limit the entropy available to Argon2id.
// Run on the application server during registration
// Required: PHP 7.3+ with libsodium support
$password = $_POST['password'];
$options = [
'memory_cost' => 65536, // 64MB
'time_cost' => 4, // Number of iterations
'threads' => 2, // Parallelism
];
$hash = password_hash($password, PASSWORD_ARGON2ID, $options);
// Store $hash in a VARCHAR(255) column in the database
Verification Flow
Verification must use password_verify. This function extracts the algorithm, cost, and salt from the stored hash string to perform the comparison securely.
// Run on the application server during login
$password = $_POST['password'];
$storedHash = $db->query("SELECT password_hash FROM users WHERE email = ?", [$email]);
if (password_verify($password, $storedHash)) {
// Password is correct
// Check if the hash needs updating due to cost changes
if (password_needs_rehash($storedHash, PASSWORD_ARGON2ID, $options)) {
$newHash = password_hash($password, PASSWORD_ARGON2ID, $options);
$db->execute("UPDATE users SET password_hash = ? WHERE id = ?", [$newHash, $userId]);
}
} else {
// Invalid credentials
}
Trust and Data Boundaries
- Untrusted Edge: Raw HTTP input is untrusted. Passwords should be transmitted over TLS and validated for length limits before processing to prevent Denial of Service (DoS) attacks via extremely long strings.
- Internal Processing: The raw password exists in memory only for the duration of the request. It must never be logged, cached, or passed to external services.
- Semi-Trusted Store: The database is treated as a store that is protected at rest but potentially vulnerable to read-access leaks. Because Argon2id is used, the database does not need to be "trusted" to keep the secret; the hash itself provides the security.
Operational Checks and Verification
Before deploying to production, you must verify that the PHP environment is compiled with the necessary libraries.
Environment Check
Run the following command in your CLI environment to ensure Argon2id is available:
php -r "print_r(password_algos());"
Expected Result: The output array must contain argon2id. If it is missing, you must install libsodium and recompile or update PHP.
Performance Monitoring
Argon2id is designed to be slow. You must monitor the 95th percentile (P95) of login request latency. If login takes longer than 500ms, it may degrade user experience or create a DoS vector where a few concurrent login requests exhaust server CPU/RAM.
Failure Modes and Design Changes
Common Failure Modes
- Low Cost Parameters: Setting
memory_costtoo low (e.g., under 16MB) makes the hash susceptible to GPU cracking. - Timing Attacks: Using
==orhash_equals()to compare a manually hashed password against a database value. Always usepassword_verify()to avoid timing side-channels. - Log Leakage: Accidentally logging
$_POSTarrays during debugging, which writes plaintext passwords to disk.
When to Change the Design
This architecture is sufficient for standard web applications. However, you should move to a different design if:
- Hardware Security Modules (HSM): Your compliance requirements demand that keys never exist in application memory; you would then move to a Pepper-based approach or an external vault.
- Passwordless Auth: You move to WebAuthn or Magic Links, rendering password hashing obsolete.
- Extreme Scale: If the CPU cost of Argon2id becomes a bottleneck for millions of concurrent logins, consider offloading authentication to a dedicated identity provider (IdP).
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.