Password Storage Tips: Securing User Credentials
This article covers essential techniques for securely storing user passwords in your applications, helping you protect sensitive user data from potential breaches.
Table of Contents
Proper password storage is a critical aspect of application security. When user credentials are compromised, the damage can extend far beyond your application due to password reuse. This guide covers fundamental techniques and best practices for securely storing passwords in your systems.
Key Takeaway
Never store passwords in plaintext. Always use modern hashing algorithms with salting to protect user credentials, even in the event of a database breach.
Password Storage Fundamentals
The core principle of secure password storage is that you should never store the actual password. Instead, you store a cryptographic hash of the password that can be used for verification without revealing the original password.
Why Not Store Plaintext Passwords?
Storing passwords in plaintext poses several serious risks:
- If your database is compromised, attackers immediately gain access to all user accounts
- Internal staff with database access can view user passwords
- Users often reuse passwords across multiple sites, so a breach of your system could compromise their accounts elsewhere
- It violates security best practices and likely breaches data protection regulations
Warning
Even encrypted passwords (as opposed to hashed) are vulnerable if the encryption key is compromised. Hashing is the preferred approach for password storage.
Secure Hashing Techniques
A cryptographic hash function converts data of arbitrary size into a fixed-size output. Good password hashing functions have these properties:
- One-way: It should be computationally infeasible to reverse the hash to obtain the original password
- Deterministic: The same input always produces the same output
- Collision-resistant: It should be extremely unlikely for two different inputs to produce the same hash
- Slow: The function should be deliberately slow to compute to resist brute-force attacks
Recommended Hashing Algorithms
Modern password hashing algorithms are specifically designed to be slow and resource-intensive to prevent brute-force attacks. The most recommended algorithms include:
| Algorithm | Pros | Cons | Recommendation |
|---|---|---|---|
| Argon2id | Winner of the Password Hashing Competition, resistant to side-channel attacks | Newer, less widely supported in some frameworks | Highly recommended |
| bcrypt | Well-established, widely supported, adaptive cost factor | Limited to 72 bytes of input, memory-constant | Recommended |
| PBKDF2 | FIPS-approved, widely available | Vulnerable to GPU/ASIC acceleration | Acceptable |
| scrypt | Memory-hard function, resistant to hardware attacks | Complex parameter selection | Recommended |
| MD5/SHA1 | Fast computation | Extremely vulnerable to brute-force attacks, cryptographically broken | Avoid |
Note
The security of password hashing depends not just on the algorithm choice, but also on proper implementation and configuration of work factors.
Salt and Pepper
Salting is a critical technique that adds an additional layer of security to password hashing.
What is a Salt?
A salt is a random value that is generated for each user and combined with their password before hashing. This ensures that even if two users have the same password, their hashed passwords will be different. Salts are typically stored alongside the password hash in the database.
How Salting Works:
- Generate a unique random salt for each user (typically 16+ bytes)
- Combine the salt with the plaintext password
- Hash the combined value
- Store both the salt and the hash in the database
- During verification, retrieve the salt, combine it with the provided password, and compare the resulting hash
What is a Pepper?
A pepper is similar to a salt but with one key difference: it's not stored in the database. Instead, it's a secret value stored in application configuration or environment variables. This adds an extra layer of protection if the database is compromised but the application server remains secure.
Implementation Tip
Most modern hashing libraries handle salt generation and storage automatically. You typically don't need to manage salts manually unless you're implementing custom cryptographic solutions.
Implementation Examples
Here are examples of how to implement secure password hashing in different programming languages:
Node.js with bcrypt
const bcrypt = require('bcrypt');
const saltRounds = 12;
// Hashing a password
async function hashPassword(plainTextPassword) {
try {
const hash = await bcrypt.hash(plainTextPassword, saltRounds);
return hash;
} catch (err) {
console.error(err);
throw err;
}
}
// Verifying a password
async function verifyPassword(plainTextPassword, hashedPassword) {
try {
const match = await bcrypt.compare(plainTextPassword, hashedPassword);
return match;
} catch (err) {
console.error(err);
throw err;
}
}
Python with Argon2
from argon2 import PasswordHasher
from argon2.exceptions import VerifyMismatchError
ph = PasswordHasher(
time_cost=3, # Number of iterations
memory_cost=65536, # Memory usage in kibibytes (64 MB)
parallelism=4, # Degree of parallelism
hash_len=32, # Length of the hash in bytes
salt_len=16 # Length of the salt in bytes
)
# Hashing a password
def hash_password(password):
return ph.hash(password)
# Verifying a password
def verify_password(password, hash):
try:
ph.verify(hash, password)
return True
except VerifyMismatchError:
return False
PHP with password_hash
// Hashing a password
function hashPassword($password) {
// PASSWORD_ARGON2ID is available in PHP 7.3+
// For older versions, use PASSWORD_BCRYPT
$hash = password_hash($password, PASSWORD_ARGON2ID, [
'memory_cost' => 65536, // 64MB
'time_cost' => 4, // 4 iterations
'threads' => 3 // 3 threads
]);
return $hash;
}
// Verifying a password
function verifyPassword($password, $hash) {
return password_verify($password, $hash);
}
Security Notice
Never implement your own cryptographic functions. Always use well-established libraries that are regularly maintained and audited by security experts.
Best Practices
Follow these guidelines to ensure your password storage implementation is as secure as possible:
Use Work Factors
Configure your hashing algorithm with appropriate work factors that balance security and performance. Increase work factors as hardware improves.
Implement Rate Limiting
Limit login attempts to prevent brute-force attacks, with exponential backoff for repeated failures.
Enforce Strong Passwords
Require minimum length (12+ characters), complexity, and check against common password lists.
Secure Transmission
Always use HTTPS to protect passwords during transmission, with proper certificate validation.
Password Upgrade Strategy
As cryptographic standards evolve, you may need to upgrade your password hashing algorithm. Here's a strategy for upgrading existing password hashes:
- Add a field to your user database to track which hashing algorithm was used
- When users log in successfully with the old algorithm, rehash their password with the new algorithm
- Update the algorithm field and store the new hash
- During verification, check the algorithm field to determine which verification method to use
Common Mistakes to Avoid
Be aware of these common password storage mistakes that can compromise security:
Using Deprecated Algorithms
MD5, SHA1, and even SHA256 are not suitable for password hashing as they're designed for speed, not security.
Insufficient Salt Length
Using short salts reduces their effectiveness. Salts should be at least 16 bytes of cryptographically secure random data.
Reusing Salts
Using the same salt for multiple users defeats the purpose of salting. Each user must have a unique salt.
Weak Work Factors
Setting work factors too low to improve performance significantly reduces security. The hash computation should take at least 250ms on your server hardware.
Security Reminder
Password hashing is just one part of a comprehensive security strategy. Implement additional measures like multi-factor authentication, secure session management, and regular security audits.
Compliance and Regulations
Different industries and regions have specific requirements for password storage. Here are some key regulations to be aware of:
| Regulation | Requirements | Applies To |
|---|---|---|
| GDPR | Requires "appropriate technical measures" for data protection, which includes secure password storage | Organizations handling EU citizens' data |
| PCI DSS | Requires strong cryptography for password protection and prohibits storing plaintext passwords | Organizations handling payment card data |
| HIPAA | Requires implementation of technical safeguards for authentication | Healthcare organizations in the US |
| NIST Guidelines | Recommends using approved algorithms (PBKDF2, bcrypt, scrypt, Argon2) with appropriate parameters | US federal agencies and recommended for others |
Compliance Tip
Always consult with a legal expert or compliance officer to ensure your password storage practices meet the specific requirements for your industry and region.
Conclusion
Secure password storage is a fundamental aspect of application security. By implementing modern hashing algorithms with proper salting, you can significantly reduce the risk of credential compromise even if your database is breached.
Remember these key points:
- Never store plaintext passwords
- Use modern, slow hashing algorithms (Argon2id, bcrypt, scrypt)
- Always salt passwords with unique, random values
- Configure appropriate work factors for your environment
- Implement a strategy for upgrading password hashes as technology evolves
By following these guidelines, you'll create a robust foundation for protecting your users' credentials and maintaining their trust in your application.