NC Logo UseToolSuite
Web Security

Password Security: Generation, Hashing, and Storage Best Practices

A complete guide to password security for developers. Learn password entropy, generation best practices, secure hashing with bcrypt and Argon2, and how to implement password policies correctly.

Necmeddin Cunedioglu Necmeddin Cunedioglu

Practice what you learn

Password Generator

Try it free →

Password Security: Generation, Hashing, and Storage Best Practices

Password security is one of the most critical responsibilities in web development. A single breach can expose millions of users. This guide covers the entire password lifecycle — from generating strong passwords to hashing and storing them securely.

Password Entropy: What Makes a Password Strong?

Password strength is measured in bits of entropy — the number of binary decisions an attacker must make to guess it. Higher entropy = harder to crack.

Entropy = log₂(N^L)

Where N is the character pool size and L is the password length.

Password TypePool Size (N)Length (L)Entropy (bits)
Lowercase only26837.6
Mixed case + digits62847.6
All printable ASCII951278.8
All printable ASCII9516105.1

A minimum of 72 bits of entropy is recommended for passwords protecting sensitive accounts. For high-security applications, aim for 90+ bits.

Generate secure passwords instantly: Our Password Generator creates cryptographically random passwords with configurable length, character sets, and entropy display.

Generating Strong Passwords

Rule 1: Use Cryptographic Randomness

Never generate passwords with Math.random(). It uses a pseudo-random number generator (PRNG) that is predictable:

// ❌ INSECURE — predictable PRNG
function badPassword(length) {
  const chars = 'abcdefghijklmnopqrstuvwxyz0123456789';
  let result = '';
  for (let i = 0; i < length; i++) {
    result += chars[Math.floor(Math.random() * chars.length)];
  }
  return result;
}

// ✅ SECURE — cryptographic randomness
function goodPassword(length) {
  const chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*';
  const array = new Uint32Array(length);
  crypto.getRandomValues(array);
  return Array.from(array, (n) => chars[n % chars.length]).join('');
}

Rule 2: Minimum 16 Characters

Shorter passwords are exponentially easier to crack. NIST SP 800-63B recommends supporting passwords up to 64 characters and requiring a minimum of 8 — but security experts recommend 16+ characters for any sensitive application.

Rule 3: Include All Character Classes

Use uppercase, lowercase, digits, and special characters. Each added character class significantly increases the search space an attacker must cover.

Hashing: bcrypt, Argon2, and scrypt

Never store passwords in plaintext. Always hash them with a slow, salted hashing algorithm designed specifically for passwords.

bcrypt

The industry standard for over 20 years. Uses the Blowfish cipher with a configurable cost factor:

import bcrypt from 'bcrypt';

const hash = await bcrypt.hash(password, 12); // cost factor 12
const valid = await bcrypt.compare(input, hash);

Hash and verify bcrypt passwords with our Bcrypt Generator & Verifier.

Argon2id

The winner of the 2015 Password Hashing Competition. Memory-hard design makes GPU attacks expensive:

import argon2 from 'argon2';

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

Which to Choose?

AlgorithmRecommendation
Argon2idBest choice for new projects
bcryptExcellent, widely supported
scryptGood alternative when Argon2 unavailable
PBKDF2Acceptable, but weaker than above
SHA-256, MD5Never use for passwords

For a deeper comparison, read our bcrypt vs SHA-256 article.

Secure Storage Practices

1. Hash on the Server, Not the Client

Client-side hashing provides no security benefit — an attacker who intercepts the hash can replay it. Always hash passwords server-side over HTTPS.

2. Never Log Passwords

Ensure password fields are excluded from request logging, error reports, and analytics:

// Express middleware — redact passwords from logs
app.use((req, res, next) => {
  if (req.body?.password) {
    req.body.password = '[REDACTED]';
  }
  next();
});

3. Implement Rate Limiting

Limit login attempts to prevent brute-force attacks. A common strategy:

  • 5 failed attempts → 30-second lockout
  • 10 failed attempts → 15-minute lockout
  • 20 failed attempts → Account lockout requiring email verification

4. Use HMAC for API Authentication

For API-to-API authentication, use HMAC (Hash-based Message Authentication Code) instead of sending raw API keys:

import { createHmac } from 'crypto';

const signature = createHmac('sha256', secretKey)
  .update(message)
  .digest('hex');

Generate and verify HMAC signatures with our HMAC Generator.

Password Policies: NIST Recommendations

NIST SP 800-63B (2024 update) provides modern password guidelines:

PracticeRecommendation
Minimum length8 characters (15+ recommended)
Maximum lengthAt least 64 characters
Complexity rulesDo not require specific character classes
Password expirationDo not force periodic changes
BlocklistCheck against known breached passwords
MFAAlways offer multi-factor authentication

The surprising shift: NIST now recommends against forced complexity rules (Must include uppercase, number, and symbol). These rules lead to predictable patterns like Password1! and don’t meaningfully increase security.

Further Reading


This article is part of our Encoding and Hashing Guide series.

Necmeddin Cunedioglu
Necmeddin Cunedioglu Author

Software developer and the creator of UseToolSuite. I write about the tools and techniques I use daily as a developer — practical guides based on real experience, not theory.