Password Hashing: The Complete Guide
What is Password Hashing?
Password hashing is a one-way transformation that converts a password into a fixed-length string of characters, which appears random. The key properties of a hash function are:
- One-way function: It should be computationally infeasible to reverse the hash to obtain the original password.
- Deterministic: The same password always produces the same hash (given the same salt).
- Collision-resistant: It should be extremely difficult to find two different passwords that produce the same hash.
- Avalanche effect: A small change in the input should result in a completely different hash output.
Password hashing is not encryption. Encryption is a two-way function meant to be reversed with the right key. Hashing is intentionally designed to be irreversible.
Why Hash Passwords?
Storing plaintext passwords is a severe security risk. When (not if) a data breach occurs, attackers immediately gain access to all user accounts. Here's why hashing is critical:
Data Breach Protection
Even if your database is compromised, attackers only get hashes, not actual passwords.
Cross-Site Protection
Prevents credential stuffing attacks since hashes can't be used on other sites.
Legal Compliance
Many regulations (GDPR, HIPAA, etc.) require proper protection of user credentials.
Zero Knowledge
System administrators and developers can't access user passwords, enhancing privacy.
In 2012, LinkedIn suffered a breach where 6.5 million unsalted SHA-1 password hashes were leaked. Because they weren't properly salted and used a fast algorithm, approximately 90% of the passwords were eventually cracked. Had they used bcrypt, the vast majority would have remained secure.
Hash Algorithms Explained
Not all hash algorithms are suitable for password storage. Modern password hashing requires specialized algorithms that are deliberately slow and resource-intensive to resist brute-force attacks.
- Adaptive work factor
- Built-in salt
- Deliberately slow
- Memory-hard function
- Parallelism control
- Highly resistant to specialized hardware
- FIPS-compliant
- Configurable iterations
- Widely supported
- Fast computation
- Requires manual salting
- Requires many iterations
- Extremely fast
- Collision vulnerabilities
- No built-in protections
For password hashing, slower is actually better. Algorithms like bcrypt and Argon2id are deliberately designed to be computationally expensive, making brute-force attacks impractical while remaining fast enough for legitimate login attempts.
The Importance of Salting
A salt is a random value that is generated for each password and combined with the password before hashing. Salting is crucial for secure password storage for several reasons:
Prevents Rainbow Table Attacks
Rainbow tables are pre-computed tables mapping hashes back to passwords. Unique salts make these tables useless since each password has a different hash.
Makes Identical Passwords Unique
If two users have the same password, their hashes will be different because each has a unique salt.
Increases Hash Complexity
Adds significant entropy to even simple passwords, making brute force attacks more difficult.
// Generate a secure random salt
$salt = bin2hex(random_bytes(16)); // 16 bytes = 32 hex characters
// Hash the password with the salt
$password = 'user_password';
$hash = hash('sha256', $salt . $password);
// Store both the salt and hash
// $storedValue = $salt . ':' . $hash;
Best Practices for Password Storage
1. Use Modern, Specialized Hash Functions
Always prefer bcrypt, Argon2id, or PBKDF2 over general-purpose hash functions like SHA-256 or MD5.
2. Include Proper Work Factors
Configure your hashing algorithm to be as slow as acceptable for your application's performance requirements. Increase work factors as hardware improves.
- For bcrypt, aim for a cost of 10-12 in 2023 (higher for more sensitive data)
- For Argon2id, start with memory = 65536 (64MB), iterations = a minimum of 3, and parallelism = 1
- For PBKDF2, use at least 310,000 iterations in 2023 (and increase yearly)
3. Implement Proper Password Validation
For verifying passwords against stored hashes, use constant-time comparison functions to prevent timing attacks.
4. Have a Hash Upgrade Strategy
As computing power increases, older hashes become more vulnerable. Implement a system to rehash passwords when users log in if their hash is stored using outdated parameters.
5. Never Truncate Passwords or Hashes
Ensure your database fields are large enough to store the full hash output. Truncation can severely compromise security.
6. Add Pepper for Extra Security (Optional)
A "pepper" is a secret key stored separately from the database that's combined with passwords before hashing. This adds another layer of protection if only the database is compromised.
First choice: Argon2id with memory=64MB, iterations=3, parallelism=1
Second choice: bcrypt with cost=12
Third choice: PBKDF2 with SHA-256 and 310,000+ iterations
Frequently Asked Questions
No, a properly implemented cryptographic hash function cannot be reversed or "decrypted." This is the fundamental property that makes hashing suitable for password storage. The only way to attack a hash is through brute force (trying every possible password) or dictionary attacks (trying common passwords). Modern hashing algorithms like bcrypt and Argon2id are specifically designed to make these attacks extremely time-consuming and impractical.
Hashing is a one-way function that transforms data into a fixed-length string, which cannot be reversed. Encryption is a two-way function that transforms data with the intention of being able to decrypt it later with the right key. For password storage, you should always use hashing, not encryption, because there should never be a need to retrieve the original password.
When you log in, the system takes the password you entered, applies the same hashing algorithm (with the same salt that was used originally), and compares the resulting hash with the stored hash. If they match, the password is correct. The system never needs to know your actual password, only whether what you entered produces the same hash.
While SHA-256 is cryptographically secure for general hashing purposes, it's designed to be fast, which is exactly what you don't want for password hashing. Specialized password hashing functions like bcrypt and Argon2id are deliberately slow and resource-intensive to prevent brute-force attacks. Additionally, general-purpose hash functions require proper salting and multiple iterations to be used safely for passwords.
You should review your password hashing approach at least annually. As computing power increases, work factors need to be adjusted upward. If you're using an older algorithm, consider migrating to a newer one when users log in. For example, if you're still using SHA-256 with iterations, plan a migration to bcrypt or Argon2id.
This is precisely why modern password hashing algorithms are important. SHA-256 and especially MD5 can be extremely fast on GPUs, making brute-force attacks feasible. Bcrypt is resistant to GPU acceleration due to its memory requirements. Argon2id goes further, with tunable memory hardness and parallelism parameters specifically designed to be resistant to specialized hardware attacks.