Best Practices for Securely Storing Passwords
Quick Summary (TL;DR)
You must never store passwords in plain text. The standard practice is to store a hashed and salted version of the password. When a user provides a password, you hash it with their unique salt and compare the result to the stored hash. A modern, slow, and memory-hard hashing algorithm like Argon2 (the current standard) or bcrypt should always be used. Older algorithms like MD5 and SHA-1 are not safe for password hashing and must be avoided.
Key Takeaways
- Hashing is One-Way: Hashing is a one-way function that transforms a password into a fixed-length string of characters, the hash. It is computationally infeasible to reverse the process and recover the original password from the hash.
- Salting Defeats Rainbow Tables: A salt is a unique, random string that is generated for each user and stored alongside their hash. The salt is combined with the password before hashing. This ensures that even if two users have the same password, their stored hashes will be different, which defeats pre-computed hash lookups (rainbow table attacks).
- Use Slow Algorithms: Fast hashing algorithms like MD5 or SHA-256 are designed for speed, which is bad for password security. It allows attackers to try billions of passwords per second. Algorithms like Argon2, bcrypt, and scrypt are intentionally slow and resource-intensive, which makes such brute-force attacks much more difficult and expensive.
The Solution
If an attacker breaches your database, they should not be able to access user accounts, even if they steal your entire user table. Storing passwords as salted hashes provides this protection. Because the original passwords cannot be recovered from the hashes, the attacker cannot simply log in as your users. Because each hash is salted with a unique salt, the attacker cannot use pre-computed tables to crack large numbers of passwords at once. By making the hashing process computationally expensive, you dramatically slow down any attempt to guess the passwords, rendering the stolen data far less useful.
Implementation Steps
Choose a Modern Hashing Algorithm Your first choice should be Argon2id, which won the Password Hashing Competition in 2015. If it is not available in your language’s standard library, bcrypt is a very strong and widely supported alternative.
Generate a Salt for New Users When a new user signs up, generate a cryptographically secure random salt (at least 16 bytes long). Store this salt in the database alongside the user’s record.
Hash the Password with the Salt Combine the user’s chosen password with their unique salt and process it through your chosen hashing algorithm (Argon2 or bcrypt). You will also need to configure a “work factor” (also called a cost factor or rounds), which controls how slow the algorithm is. A higher work factor is more secure. Store the resulting hash in the user’s database record.
Verify the Password on Login When a user attempts to log in, retrieve their stored salt and hash from the database. Combine the password they just provided with their stored salt and run it through the exact same hashing algorithm and work factor. If the resulting hash matches the hash stored in the database, the password is correct.
Common Questions
Q: Why can’t I just use SHA-256? SHA-256 is a fast cryptographic hash function. Its speed is a feature for data integrity checks but a critical flaw for password hashing. Modern GPUs can compute billions of SHA-256 hashes per second, making it relatively easy to brute-force passwords hashed with it. Slow algorithms are essential.
Q: What is “peppering”? A “pepper” is a secret value that is added to the password along with the salt before hashing. Unlike the salt, the pepper is the same for all users and is kept secret from the database (e.g., stored in an environment variable or a secrets manager). It adds another layer of security, as an attacker would need to compromise both the database and the application server to crack passwords.
Q: How do I choose the work factor for bcrypt/Argon2? The work factor should be set as high as you can tolerate without negatively affecting the user experience. A good starting point is a value that results in the hashing operation taking around 100-500 milliseconds on your hardware. You should increase this value over time as computing power gets cheaper.
Tools & Resources
- OWASP Password Storage Cheat Sheet: The definitive, in-depth guide to password storage best practices from OWASP.
- Your Language’s Standard Library: Most modern languages and security libraries have built-in, easy-to-use functions for bcrypt or Argon2 that handle salt generation and verification for you. Always use these standard implementations rather than trying to build your own.
- Argon2: The official website for the winner of the Password Hashing Competition.
Related Topics
Authentication & API Security
- A Guide to API Authentication with OAuth 2.0 and JWTs
- Building Secure APIs from Scratch
- API Authentication and Authorization Patterns
- API Security Best Practices
Application Security
Web Security Vulnerabilities
- Understanding Cross-Site Scripting (XSS): A Guide to Prevention
- Preventing Cross-Site Request Forgery (CSRF) Attacks
Security Headers & Policies
Need Help With Implementation?
Correctly implementing password security is non-negotiable for any application that handles user accounts. Built By Dakic provides application security audits and secure development training to ensure your team is following the latest best practices for protecting user data. Get in touch for a free consultation.