Skip to main content
DevBench
All articles
securityjavascript

How to Generate Secure Passwords: A Developer's Guide

May 15, 20265 min read

Passwords fail for two reasons: they are too short to withstand brute force, or they come from a predictable source. A truly secure password is long, drawn from a large character set, and generated by a cryptographically strong random number generator — not Math.random(), a human brain, or a pattern like P@ssword1!.

What makes a password secure: entropy

Password strength is measured in bits of entropy — the number of equally likely possibilities an attacker must search. A brute-force attacker tries possibilities at a rate determined by their hardware; entropy determines how large the search space is.

LengthCharacter setEntropy (bits)Verdict
8 charslowercase only (26)~37.6Crackable in seconds
8 charsupper + lower + digits (62)~47.6Hours to days
12 charsupper + lower + digits + symbols (94)~78.7Years on current hardware
16 charssame 94-char set~105Astronomically large
5 wordsdiceware (7776 word list)~64.6Strong and memorable

Entropy formula: bits = length × log₂(charsetSize). Aim for at least 80 bits for standard accounts; 128 bits or more for critical credentials.

Why Math.random() is wrong for passwords

JavaScript's Math.random() is a pseudo-random number generator (PRNG) seeded from a predictable source. An attacker who knows the seed can reproduce your entire output. For password generation you need a cryptographically secure PRNG (CSPRNG) backed by the operating system's entropy pool.

// ❌ Never use for passwords
const random = Math.random(); // not cryptographically secure

// ✅ Use the Web Crypto API
const array = new Uint32Array(1);
crypto.getRandomValues(array);
const randomValue = array[0];

Generating a secure password in JavaScript

The Web Crypto API is available in all modern browsers and Node.js 15+. Here is a clean, unbiased implementation:

function generatePassword(length = 16, options = {}) {
  const { upper = true, lower = true, digits = true, symbols = true } = options;
  let charset = "";
  if (upper)   charset += "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
  if (lower)   charset += "abcdefghijklmnopqrstuvwxyz";
  if (digits)  charset += "0123456789";
  if (symbols) charset += "!@#$%^&*()-_=+[]{}|;:,.<>?";

  const bytes = new Uint32Array(length);
  crypto.getRandomValues(bytes);

  // Modulo bias fix: rejection sampling
  const max = Math.floor(0xFFFFFFFF / charset.length) * charset.length;
  const chars: string[] = [];
  let i = 0;
  while (chars.length < length) {
    const val = bytes[i % length];
    if (val < max) chars.push(charset[val % charset.length]);
    i++;
    if (i >= length && chars.length < length) {
      crypto.getRandomValues(bytes); i = 0;
    }
  }
  return chars.join("");
}

The rejection-sampling step avoids modulo bias — a subtle flaw where values near the end of the character set are slightly more likely when the charset size does not evenly divide the random range.

Generating a secure password in Python

import secrets
import string

def generate_password(length=16):
    alphabet = string.ascii_letters + string.digits + string.punctuation
    return "".join(secrets.choice(alphabet) for _ in range(length))

print(generate_password(20))

Python's secrets module (added in 3.6) is the correct tool for security-sensitive random generation. random.choice() is the Python equivalent of Math.random() — do not use it for credentials.

On the command line

# macOS / Linux — openssl
openssl rand -base64 24

# Linux — /dev/urandom
head -c 32 /dev/urandom | base64

# Cross-platform — pwgen
pwgen -s 20 1

Passphrases vs random strings

A five-word diceware passphrase like correct-horse-battery-staple-bridge has ~65 bits of entropy, is more memorable than a random string, and survives dictionary attacks if words are chosen from a large enough wordlist (7776+ words from physical dice rolls, not a guessable pattern). Use passphrases for human-typed credentials; use random strings for API keys and machine credentials.

Where not to store passwords

  • Plain text files or spreadsheets — if the file leaks, every credential is exposed instantly.
  • Environment variables in version control.env files with real secrets should never be committed.
  • Sticky notes or browser autofill for shared credentials — use a team password manager with audit logs.

For server-side storage of user passwords, never store plaintext or reversible encryption. Hash with a purpose-built password hashing function: bcrypt, scrypt, or Argon2id.

Try it yourself

Use the free browser-based Password Generator on DevBench — no signup, runs entirely in your browser.

Open Password Generator