How to Generate Secure Passwords: A Developer's Guide
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.
| Length | Character set | Entropy (bits) | Verdict |
|---|---|---|---|
| 8 chars | lowercase only (26) | ~37.6 | Crackable in seconds |
| 8 chars | upper + lower + digits (62) | ~47.6 | Hours to days |
| 12 chars | upper + lower + digits + symbols (94) | ~78.7 | Years on current hardware |
| 16 chars | same 94-char set | ~105 | Astronomically large |
| 5 words | diceware (7776 word list) | ~64.6 | Strong 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 —
.envfiles 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