Password Security in 2026: Best Practices for Developers

Passwords are still the primary authentication method for most applications. This guide covers how to hash, salt, validate, and store passwords correctly — plus when to move beyond passwords entirely with 2FA and passkeys.

Why Password Security Still Matters

In 2024, the RockYou2024 compilation exposed nearly 10 billion unique plaintext passwords from decades of data breaches. The Snowflake breach chain compromised over 165 organizations because stolen credentials lacked multi-factor authentication. Despite years of industry efforts toward passwordless login, passwords remain the primary authentication method for the vast majority of web applications.

As a developer, the passwords your users trust you with are a liability. Store them correctly, and a database breach is an inconvenience. Store them wrong, and it becomes front-page news. The difference is entirely in how you handle three things: hashing, validation policies, and layered authentication.

This guide covers each one with working code examples. You can test password strength and generate secure passwords as you read using QTool's Password Strength Checker and Password Generator.

Password Hashing: bcrypt, Argon2, scrypt

A hash function takes an input of any length and produces a fixed-length output. Cryptographic hash functions are one-way: given the output, you cannot compute the input. Password hashing adds a critical requirement: the function must be intentionally slow.

Fast hash functions like MD5 and SHA-256 compute billions of hashes per second on modern GPUs. An attacker with a stolen database can try every common password in minutes. Password-specific hash functions are designed to be slow (hundreds of milliseconds per hash), making brute-force attacks computationally infeasible.

Algorithm Comparison

Algorithm Year Memory-Hard GPU Resistant Recommendation
Argon2id 2015 Yes Yes Best choice for new projects
bcrypt 1999 No (4 KB) Moderate Excellent, widely supported
scrypt 2009 Yes Yes Good alternative to Argon2
PBKDF2 2000 No No Acceptable if nothing else available
SHA-256 2001 No No Never use for passwords
MD5 1992 No No Never use for passwords
Never Use Fast Hashes for Passwords

MD5, SHA-1, and SHA-256 were designed to be fast. A single RTX 4090 GPU computes ~8 billion MD5 hashes per second. That means every 8-character password using lowercase letters and digits (2.8 trillion combinations) can be cracked in under 6 minutes. Use bcrypt, Argon2id, or scrypt.

Argon2id

Argon2 won the Password Hashing Competition (PHC) in 2015 and is the OWASP-recommended algorithm. The id variant combines Argon2i (resistant to side-channel attacks) and Argon2d (resistant to GPU attacks). You configure three parameters independently:

OWASP's recommended minimum for Argon2id: 19 MiB memory, 2 iterations, 1 parallelism. For higher security: 64 MiB memory, 3 iterations, 4 parallelism. Tune until hashing takes 200-500ms on your hardware.

bcrypt

bcrypt has been the industry standard since 1999. It uses a single parameter called cost factor (also called work factor or rounds), expressed as a power of 2. A cost of 12 means 2^12 = 4,096 iterations. Each increment doubles the computation time.

In 2026, use a minimum cost factor of 12 (roughly 250ms on modern hardware). If your server can handle it, use 13 or 14. bcrypt has a 72-byte input limit — passwords longer than 72 bytes are silently truncated. If you need to support longer passwords, pre-hash with SHA-256 before passing to bcrypt.

scrypt

scrypt was designed specifically to be memory-hard. It takes three parameters: N (CPU/memory cost, must be a power of 2), r (block size), and p (parallelism). Recommended values: N=2^15, r=8, p=1, which uses about 32 MB of memory per hash.

Salting: What It Is and How It Works

A salt is a random value unique to each user, combined with their password before hashing. Without salting, two users with the same password produce the same hash — meaning an attacker who cracks one has cracked both.

pseudocode — Why Salting Matters
// Without salt: identical passwords produce identical hashes
hash("password123") = "ef92b778..."
hash("password123") = "ef92b778..."  // Same! Attacker cracks one, gets both.

// With unique salts: identical passwords produce different hashes
hash("a1b2c3d4" + "password123") = "7f2e9c3a..."
hash("x9y8z7w6" + "password123") = "1d4e5f6a..."  // Different!

Salting defeats two attack strategies:

bcrypt and Argon2 Handle Salts Automatically

Both bcrypt and Argon2 generate a cryptographically random salt and embed it in the output hash string. You do not need to generate or store salts separately. Just pass the password in and store the full output string.

Implementation: Hashing in Code

Node.js: bcrypt

javascript — bcrypt with the bcrypt Package
import bcrypt from 'bcrypt';

const SALT_ROUNDS = 12; // 2^12 iterations, ~250ms

// Hash a password (at registration or password change)
async function hashPassword(plaintext) {
  return bcrypt.hash(plaintext, SALT_ROUNDS);
}

// Verify a password (at login)
async function verifyPassword(plaintext, storedHash) {
  return bcrypt.compare(plaintext, storedHash);
}

// Usage
const hash = await hashPassword('my-secure-password');
// "$2b$12$LJ3m4ys3Lk0TSwHjZx.G5e8K7Yf2cVDq9xlmPJ6xUq4aExZnSCXi"

const isValid = await verifyPassword('my-secure-password', hash);
// true

Node.js: Argon2

javascript — Argon2id with the argon2 Package
import argon2 from 'argon2';

async function hashPassword(plaintext) {
  return argon2.hash(plaintext, {
    type: argon2.argon2id,
    memoryCost: 65536,  // 64 MiB
    timeCost: 3,        // 3 iterations
    parallelism: 4
  });
}

async function verifyPassword(plaintext, storedHash) {
  return argon2.verify(storedHash, plaintext);
}

const hash = await hashPassword('my-secure-password');
// "$argon2id$v=19$m=65536,t=3,p=4$c29tZXNhbHQ$..."

const isValid = await verifyPassword('my-secure-password', hash);
// true

Python: bcrypt and Argon2

python — bcrypt
import bcrypt

def hash_password(plaintext: str) -> str:
    salt = bcrypt.gensalt(rounds=12)
    hashed = bcrypt.hashpw(plaintext.encode('utf-8'), salt)
    return hashed.decode('utf-8')

def verify_password(plaintext: str, stored_hash: str) -> bool:
    return bcrypt.checkpw(
        plaintext.encode('utf-8'),
        stored_hash.encode('utf-8')
    )
python — Argon2id
from argon2 import PasswordHasher

ph = PasswordHasher(
    memory_cost=65536,  # 64 MiB
    time_cost=3,
    parallelism=4
)

def hash_password(plaintext: str) -> str:
    return ph.hash(plaintext)

def verify_password(plaintext: str, stored_hash: str) -> bool:
    try:
        return ph.verify(stored_hash, plaintext)
    except Exception:
        return False

Go: bcrypt

go — bcrypt with golang.org/x/crypto
package main

import (
    "fmt"
    "golang.org/x/crypto/bcrypt"
)

func hashPassword(password string) (string, error) {
    bytes, err := bcrypt.GenerateFromPassword(
        []byte(password),
        12, // cost factor
    )
    return string(bytes), err
}

func verifyPassword(password, hash string) bool {
    err := bcrypt.CompareHashAndPassword(
        []byte(hash),
        []byte(password),
    )
    return err == nil
}

func main() {
    hash, _ := hashPassword("my-secure-password")
    fmt.Println("Hash:", hash)
    fmt.Println("Valid:", verifyPassword("my-secure-password", hash))
}

To quickly generate hash values for testing, use QTool's Hash Generator which supports MD5, SHA-1, SHA-256, SHA-512, and more — all computed client-side without sending your data anywhere.

Password Policies That Actually Work

NIST Special Publication 800-63B (Digital Identity Guidelines, updated 2024) fundamentally changed password policy recommendations. Many of the rules developers have enforced for decades are now explicitly discouraged.

What NIST Recommends

What NIST Explicitly Discourages

Password Entropy in Practice

A random 8-character password using all 95 printable ASCII characters has ~52 bits of entropy. A random 4-word passphrase from a 7,776-word list (diceware) has ~51 bits. But the passphrase is far easier to remember. Check the entropy of any password instantly with QTool's Password Strength Checker.

Checking Against Known Breaches

The Have I Been Pwned (HIBP) Passwords API lets you check if a password has appeared in any known data breach without ever transmitting the password or its full hash.

javascript — Check Password Against HIBP (k-Anonymity)
async function isPasswordBreached(password) {
  // 1. SHA-1 hash the password
  const encoder = new TextEncoder();
  const data = encoder.encode(password);
  const hashBuffer = await crypto.subtle.digest('SHA-1', data);
  const hashArray = Array.from(new Uint8Array(hashBuffer));
  const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join('').toUpperCase();

  // 2. Send only the first 5 characters
  const prefix = hashHex.slice(0, 5);
  const suffix = hashHex.slice(5);

  // 3. Get all matching hashes from HIBP
  const response = await fetch(`https://api.pwnedpasswords.com/range/${prefix}`);
  const text = await response.text();

  // 4. Check if our full hash suffix appears
  const lines = text.split('\n');
  for (const line of lines) {
    const [hashSuffix, count] = line.split(':');
    if (hashSuffix.trim() === suffix) {
      return { breached: true, count: parseInt(count) };
    }
  }

  return { breached: false, count: 0 };
}

// Usage
const result = await isPasswordBreached('password123');
// { breached: true, count: 247543 }

Integrate this check at two points: registration (block the password) and password change (block or warn). Some teams also check at login and prompt users to change compromised passwords, though this requires careful UX to avoid locking users out.

Two-Factor Authentication (2FA)

A strong password hash protects against database breaches. Two-factor authentication protects against stolen passwords — phishing, keyloggers, credential stuffing, and social engineering. Every application that handles sensitive data should offer 2FA.

TOTP (Time-Based One-Time Password)

TOTP is the most widely supported 2FA method. The user scans a QR code with an authenticator app (Google Authenticator, Authy, 1Password). The app generates a 6-digit code that changes every 30 seconds.

javascript — TOTP Setup with otpauth
import { TOTP } from 'otpauth';
import QRCode from 'qrcode';

// Generate a secret for the user (at 2FA enrollment)
function createTOTPSecret(userEmail) {
  const totp = new TOTP({
    issuer: 'YourApp',
    label: userEmail,
    algorithm: 'SHA1',
    digits: 6,
    period: 30
  });

  return {
    secret: totp.secret.base32,     // Store encrypted in DB
    uri: totp.toString(),           // For QR code
    qrCode: QRCode.toDataURL(totp.toString()) // QR image
  };
}

// Validate a code (at login)
function verifyTOTP(secret, userCode) {
  const totp = new TOTP({
    secret: OTPAuth.Secret.fromBase32(secret),
    algorithm: 'SHA1',
    digits: 6,
    period: 30
  });

  // Allow 1 step of drift (previous + current + next period)
  const delta = totp.validate({ token: userCode, window: 1 });
  return delta !== null; // null means invalid
}

Backup Codes

Always generate 8-10 single-use backup codes during 2FA enrollment. Store them hashed (bcrypt) in the database. When a user enters a backup code, verify it, then mark it as used. Display them once during setup and instruct the user to store them securely. This is the recovery path when a user loses their authenticator device.

WebAuthn / Security Keys

WebAuthn (FIDO2) provides the strongest second factor: hardware security keys (YubiKey, Google Titan) or platform authenticators (Touch ID, Windows Hello). WebAuthn is phishing-resistant by design because the browser verifies the origin domain before signing the challenge.

Avoid SMS-Based 2FA as the Only Option

SIM swapping attacks are well-documented and have compromised high-value accounts. Offer TOTP or WebAuthn as the primary 2FA method. SMS is still better than no 2FA at all, but it should not be the only option for security-sensitive applications.

Passkeys: The Future of Authentication

Passkeys are a passwordless authentication standard built on WebAuthn/FIDO2. Instead of typing a password, users authenticate with their device's biometric sensor, a PIN, or a security key. The private key never leaves the device (or is synced via the platform's encrypted cloud keychain).

How Passkeys Work

  1. Registration: The server sends a challenge. The browser creates a public/private key pair. The private key is stored on the device. The public key is sent to the server.
  2. Authentication: The server sends a challenge. The browser signs it with the private key (after biometric/PIN verification). The server verifies the signature with the stored public key.

Platform Support (February 2026)

javascript — Passkey Registration (SimpleWebAuthn)
import {
  generateRegistrationOptions,
  verifyRegistrationResponse
} from '@simplewebauthn/server';

// Server: Generate registration options
const options = await generateRegistrationOptions({
  rpName: 'Your App',
  rpID: 'yourapp.com',
  userName: user.email,
  attestationType: 'none',
  authenticatorSelection: {
    residentKey: 'preferred',
    userVerification: 'preferred'
  }
});

// Client: navigator.credentials.create(options)
// Server: Verify the response
const verification = await verifyRegistrationResponse({
  response: clientResponse,
  expectedChallenge: options.challenge,
  expectedOrigin: 'https://yourapp.com',
  expectedRPID: 'yourapp.com'
});

if (verification.verified) {
  // Store verification.registrationInfo in your database
}

Generate secure test passwords while implementing these features with QTool's Password Generator, which creates cryptographically random passwords of any length and character set.

Secure Storage and Transmission

In Transit

At Rest

Test Your Password Strength

QTool's Password Strength Checker estimates crack time, calculates entropy, and checks against breach databases. Entirely client-side — your password never leaves your browser.

Check Password Strength Generate Passwords

Security Tools


Frequently Asked Questions

Argon2id is the recommended choice for new projects in 2026. It won the Password Hashing Competition in 2015 and has been the OWASP recommendation since 2023. Argon2id is memory-hard, making it resistant to both GPU and ASIC attacks, and it lets you configure memory usage, parallelism, and iteration count independently. However, bcrypt remains a solid choice if your platform does not have a well-tested Argon2 library. bcrypt has been battle-tested for over 25 years and is supported everywhere. The most important thing is to use any of the recommended algorithms (Argon2id, bcrypt, or scrypt) rather than MD5, SHA-1, SHA-256, or any unsalted/fast hash.

A salt is a random string that is appended or prepended to a password before hashing. Each user gets a unique salt, which is stored alongside the hash in the database. Salting prevents two users with the same password from having the same hash (defeating rainbow table attacks), and it makes precomputed hash tables useless. Without salting, an attacker who obtains your database can look up common password hashes in a rainbow table and crack millions of passwords instantly. With unique salts, each password must be attacked individually. Modern hashing functions like bcrypt and Argon2 generate and embed the salt automatically, so you do not need to manage salts manually.

NIST SP 800-63B recommends: minimum 8 characters (15+ is better), maximum at least 64 characters, allow all printable ASCII characters plus Unicode, check against a list of known breached passwords (like the Have I Been Pwned database), do NOT require specific character classes (uppercase, lowercase, digit, symbol), and do NOT force periodic password changes. The reasoning is that complexity rules lead users to create predictable patterns like "Password1!" while a long passphrase like "correct horse battery staple" is both stronger and easier to remember.

Implement TOTP (Time-based One-Time Password) as the baseline 2FA method. Use a library that generates a shared secret, creates a QR code for authenticator apps, and validates 6-digit codes with a time window of plus or minus one step (30 seconds). Store the shared secret encrypted in your database. Always provide backup codes (8-10 single-use codes) during setup. For higher security, support WebAuthn/FIDO2 security keys. Avoid SMS-based 2FA as the primary option because SIM swapping attacks are well-documented, though SMS is still better than no 2FA at all.

Passkeys are a passwordless authentication standard built on WebAuthn/FIDO2. Instead of a password, users authenticate with their device's biometric sensor (fingerprint, face), a PIN, or a security key. The cryptographic key pair is stored on the user's device (or synced via their platform's cloud keychain), and only the public key is stored on the server. Passkeys are phishing-resistant by design because the browser verifies the domain. As of 2026, passkeys are supported by Apple, Google, Microsoft, and all major browsers. You should support passkeys alongside traditional passwords.

Use the Have I Been Pwned (HIBP) Passwords API with k-anonymity. Hash the password with SHA-1, send only the first 5 characters of the hash to the API, and receive a list of matching hash suffixes. Check if the full hash appears in the response. This approach never sends the actual password or the full hash to any external service. The API is free and returns the number of times each password has appeared in known breaches. Block any password that has appeared in more than 0 breaches, or at minimum warn the user and require confirmation.

NT

Christian Bucher

We build free developer tools including password generators, hash tools, and security utilities. 269 tool pages, all browser-based, no signup required.

269 Developer Tools, One Place

Browse 269 indexed tool pages with no QTool account required, and inspect the source on GitHub.

Open Source — Free Forever Try Free Tools

Related Tools

CSS Box Shadow Generator · Emoji Picker & Search · Free Base64 Encoder/Decoder

Related Tools

Random Generator · Free API Mock Server · Free Color Palette Generator

Related Articles

Built by Miguel

Need a custom tool or website?

From . Delivered in 24-48h. You own the code.

View Services →