HVRDHVRD
JavaScript

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);  // true

This 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);  // false

Why 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

MethodProsCons
Plain TextEasy implementationInsecure, catastrophic if leaked
Hashing Only (e.g., SHA256)Better than plain textSame hash for same password, vulnerable to rainbow tables
Hashing + SaltMitigates identical hash issueManual implementation risk
bcryptRecommendedSlightly 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.