Password Security in 2026: Hashing, Salting, and Storage
SecurityPasswordAuthentication

Password Security in 2026: Hashing, Salting, and Storage

Never Store Plaintext Passwords

This should go without saying in 2026, yet plaintext password storage still appears in breach reports every year. If your database is compromised, plaintext passwords expose every user immediately and completely. Worse, because people reuse passwords across services, the damage extends far beyond your application — you have handed attackers the keys to your users' email, banking, and social accounts too.

The fundamental principle: no one at your company should ever be able to read a user's password — not engineers, not support staff, not the CEO. If a user asks "what is my password?", the only correct answer is "I cannot tell you, but I can send you a reset link."

Why MD5 and SHA-256 Are Completely Unacceptable

In the early days of web development, it was common to store MD5(password) or SHA256(password). These algorithms were never designed for password hashing — they were designed to be fast, which is the opposite of what you want.

A modern consumer GPU can compute over 10 billion MD5 hashes per second. A leaked database of MD5-hashed passwords can be cracked in hours or days using brute force or precomputed rainbow tables. SHA-256 fares slightly better due to longer hash length, but at 3+ billion hashes per second on modern hardware, it is equally unacceptable for passwords.

Even with a random salt (to defeat rainbow tables), a fast hash allows attackers to enumerate massive password spaces quickly. The defense is algorithmic slowness — using a hashing function that is intentionally slow and resource-intensive.

The Right Algorithms: bcrypt, Argon2, scrypt

bcrypt

Designed by Niels Provos and David Mazières in 1999, bcrypt was specifically designed for password hashing. Its defining feature is a configurable "cost factor" (also called work factor) — an exponent that controls how many rounds of hashing are performed. Doubling the cost factor doubles the computation time.

The recommended cost factor today is 12, which means 2^12 = 4096 rounds. On modern hardware, this takes ~100–300ms per hash — imperceptibly slow for a user logging in, but devastating for an attacker trying to check millions of password candidates.

import bcrypt from 'bcrypt';

// Hashing
const COST_FACTOR = 12;
const hash = await bcrypt.hash(plainTextPassword, COST_FACTOR);

// Verification (always use the library's compare — never hash and compare directly)
const isValid = await bcrypt.compare(userInput, storedHash);

bcrypt automatically generates a cryptographically random salt and includes it in the hash output. You do not need to manage salts separately.

Argon2id

Argon2 won the 2015 Password Hashing Competition — the most rigorous public evaluation of password hashing algorithms ever conducted. The id variant (Argon2id) is the recommended choice: it provides protection against both GPU attacks (through memory-hardness) and side-channel attacks.

Argon2 has three parameters: time cost (iterations), memory cost (KB of RAM used), and parallelism (threads). The memory-hardness is the key advantage over bcrypt — filling gigabytes of RAM makes GPU-based attacks orders of magnitude more expensive since GPUs have limited VRAM.

import argon2 from 'argon2';

const hash = await argon2.hash(password, {
  type: argon2.argon2id,
  memoryCost: 65536,    // 64 MB
  timeCost: 3,          // 3 iterations
  parallelism: 4,
});

const isValid = await argon2.verify(hash, userInput);

If you are starting a new project in 2026, Argon2id is the correct choice. For existing projects using bcrypt, bcrypt is still secure — there is no urgent need to migrate.

scrypt

scrypt is also memory-hard and computationally expensive. It is built into many languages' standard libraries (Node.js includes it natively via crypto.scrypt). It is a solid choice when you prefer to avoid external dependencies, but Argon2id is generally preferred for new projects due to its more straightforward parameter tuning.

Salting Is Essential — And Automatic

A salt is a random value added to the password before hashing. It ensures that two users with the same password produce different hashes, and that precomputed rainbow tables are useless. bcrypt, Argon2, and scrypt all generate and include a unique salt automatically in their output — you do not need to generate or store salts separately. Never implement your own salting logic; always use the library's built-in functions.

Pepper: An Additional Layer

A pepper is a secret value (stored outside the database, in an environment variable or secrets manager) that is combined with the password before hashing. Unlike salts, peppers are the same across all users — they add a layer of defense if the database is leaked but the application server is not.

const PEPPER = process.env.PASSWORD_PEPPER; // never hardcode this
const hash = await argon2.hash(password + PEPPER);

Peppers are optional but add meaningful defense in depth. If an attacker gets your database but not your application secrets, the password hashes are useless without the pepper.

Password Reset Implementation

Password reset flows are a common attack target. The secure pattern:

  1. Generate a cryptographically random token: crypto.randomBytes(32).toString('hex')
  2. Hash the token with SHA-256 and store the hash (not the raw token) in the database, with an expiry of 15–60 minutes
  3. Email the raw token to the user as a URL parameter
  4. On receipt, hash the URL token and compare to the stored hash
  5. If valid and not expired, allow password reset and immediately invalidate the token

Never send passwords in reset emails. The token is a one-time key that proves control of the email address — nothing more.

Testing Your Hashes

Use PureFormatter's Bcrypt Hash tool to generate and verify bcrypt hashes during development and testing. Never store test passwords in plaintext in your codebase — even in test fixtures.

Fredy
Written by
Fredy
Senior Developer & Technical Writer

Fredy is a full-stack developer with 8+ years of experience building web applications. He writes about developer tools, best practices, and the craft of clean code.