Secure Password Storage
In-depth guide on why storing plain-text passwords is dangerous, how hashing works, its limitations, introduction to salting, and how bcrypt solves security challenges.
Storing user passwords securely is one of the most critical aspects of building any application that involves authentication. Mishandling password storage can lead to catastrophic data breaches and user data leaks.
Why Storing Plain-Text Passwords is a Security Issue
If passwords are stored in plain text, anyone who gains access to your database—whether through hacking, insider threat, or misconfigured backups—can directly read every user's password. This is a nightmare scenario because:
- Many users reuse passwords across services.
- A single breach compromises not only your application but potentially others where the user reuses the same password.
- You lose all control over protecting user credentials.
Example of Insecure Storage
// BAD PRACTICE: Storing plain-text passwords
const userSchema = new mongoose.Schema({
username: String,
password: String, // Stored as-is
});Once the database is leaked, an attacker immediately has full access.
Hashing Passwords
Hashing is a one-way cryptographic function that converts data (like a password) into a fixed-length string of characters. This process is irreversible.
Example using SHA256
const crypto = require('crypto');
function hashPassword(password) {
return crypto.createHash('sha256').update(password).digest('hex');
}
const hashed = hashPassword('supersecret');
console.log(hashed);
// Example output: 'e99a18c428cb38d5f260853678922e03abd833b7e5a41b7f65b98d3d4b5f01f1'Why Hashing is Better
- The password is not stored directly.
- Even if the database is leaked, attackers have to brute-force to guess the original password.
Problem with Hashing Alone
Using just hashing (like SHA256) has a major flaw: Deterministic output.
If two users have the same password, their hash will be identical.
const hash1 = hashPassword('password123');
const hash2 = hashPassword('password123');
console.log(hash1 === hash2); // trueThis makes it easier for attackers to perform dictionary attacks or use precomputed tables (rainbow tables).
Enter Salting
A Salt is random data appended or prepended to the password before hashing. This ensures that even if two users have the same password, their hashes will be different.
How Salting Works
function hashWithSalt(password, salt) {
return crypto.createHash('sha256').update(password + salt).digest('hex');
}
const salt1 = crypto.randomBytes(16).toString('hex');
const salt2 = crypto.randomBytes(16).toString('hex');
const hash1 = hashWithSalt('password123', salt1);
const hash2 = hashWithSalt('password123', salt2);
console.log(hash1 === hash2); // falseWhy Salting Helps
- Prevents identical hashes for identical passwords.
- Defends against precomputed attacks.
- Each user gets a unique salt.
Storing Salt
Salt must be stored along with the hash in the database.
Example Schema Design:
const userSchema = new mongoose.Schema({
username: String,
passwordHash: String,
salt: String,
});Why Use bcrypt Instead of DIY Hashing + Salting
bcrypt is a secure password hashing function designed specifically for password storage. It automatically handles salting, key stretching, and is computationally expensive to slow down brute-force attacks.
Using bcrypt
const bcrypt = require('bcrypt');
// Hashing a password
const saltRounds = 10;
bcrypt.hash('supersecret', saltRounds, (err, hash) => {
if (err) throw err;
console.log('Hashed password:', hash);
});Verifying a Password
bcrypt.compare('supersecret', hashedPasswordFromDB, (err, isMatch) => {
if (err) throw err;
console.log('Password match:', isMatch); // true or false
});Why bcrypt is Better
- Automatically handles salting internally.
- Adjustable cost factor (
saltRounds) to increase computational complexity over time. - Designed to be slow, making brute-force and rainbow table attacks impractical.
Summary
| Method | Pros | Cons |
|---|---|---|
| Plain Text | Easy implementation | Insecure, catastrophic if leaked |
| Hashing Only (e.g., SHA256) | Better than plain text | Same hash for same password, vulnerable to rainbow tables |
| Hashing + Salt | Mitigates identical hash issue | Manual implementation risk |
| bcrypt | Recommended | Slightly slower but secure and standardized |
Best Practice Recommendation
Always use a well-tested library like bcrypt for password storage. Never roll your own salting or hashing solution unless you deeply understand cryptography.