Why Password Generation Matters
A strong password is the first line of defense for any online account. But generating a truly random, unguessable password is harder than it seems — especially in software. The choice of random number generator determines whether your password generator is genuinely secure or just looks secure.
The Problem with Math.random()
JavaScript's Math.random() returns a pseudo-random number between 0 and 1. It is implemented as a deterministic algorithm (typically xorshift128+ in V8) seeded from the system clock or OS entropy. This means:
- The output is predictable if the seed is known or guessable
- It is not suitable for security-sensitive purposes like password generation
- Cryptographic analysis of observed outputs can reveal the internal state
// WRONG — never use this for passwords
const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
const password = Array.from({ length: 16 }, () =>
chars[Math.floor(Math.random() * chars.length)]
).join("");
The Right Tool: crypto.getRandomValues()
window.crypto.getRandomValues() is the browser's interface to the operating system's cryptographically secure pseudo-random number generator (CSPRNG). On Linux, this pulls from /dev/urandom; on Windows, from BCryptGenRandom; on macOS, from SecRandomCopyBytes. These are the same sources used for TLS key generation.
// CORRECT — cryptographically secure
function getSecureRandomInt(max: number): number {
const array = new Uint32Array(1);
let result: number;
do {
crypto.getRandomValues(array);
result = array[0];
} while (result >= Math.floor(0xFFFFFFFF / max) * max); // rejection sampling
return result % max;
}
const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*";
const password = Array.from({ length: 16 }, () =>
chars[getSecureRandomInt(chars.length)]
).join("");
The rejection sampling loop is important — naive modulo (value % max) introduces modulo bias when max doesn't evenly divide the range, making some characters slightly more likely than others. Rejection sampling eliminates this bias.
Password Entropy: Measuring Strength
Password strength is measured in bits of entropy. Entropy quantifies unpredictability: a password with H bits of entropy requires on average 2^(H-1) guesses to crack by brute force.
The formula for a randomly generated password:
H = L × log₂(N)
// L = password length
// N = size of the character set
// H = entropy in bits
Examples with different character sets:
| Length | Lowercase only (26) | Lower+Upper+Digits (62) | Full ASCII (95) |
|---|---|---|---|
| 8 | 37.6 bits | 47.6 bits | 52.6 bits |
| 12 | 56.4 bits | 71.5 bits | 78.9 bits |
| 16 | 75.2 bits | 95.3 bits | 105.2 bits |
| 20 | 94.0 bits | 119.1 bits | 131.5 bits |
Security guidelines generally recommend 80+ bits of entropy for high-value accounts. A 16-character password from the full printable ASCII set (95 characters) provides 105 bits — far beyond the reach of offline brute-force attacks with current hardware.
Passphrases: High Entropy, Human-Friendly
A passphrase selects random words from a large dictionary (the EFF Large Wordlist has 7,776 words — 6 dice rolls per word). A 6-word passphrase provides:
H = 6 × log₂(7776) = 6 × 12.925 ≈ 77.5 bits
This is comparable to a 13-character random password from a 62-character set — and far easier for humans to memorize. The famous XKCD "correct horse battery staple" comic popularized this approach. With random word selection (not deliberate choices), passphrases are highly secure.
Common Password Requirements and Their Tradeoffs
Many password policies require uppercase, numbers, and symbols. While this broadens the character set (increasing entropy per character), it often reduces security in practice because users compensate with predictable patterns: Password1!, Summer2024#.
A randomly generated 16-character password without special characters is objectively stronger than a 10-character password that technically meets complexity requirements. Length beats complexity for human-chosen passwords; for generated passwords, both matter.
Password Generation in Practice
Use the Copynix Password Generator to generate cryptographically secure passwords entirely in your browser. It uses crypto.getRandomValues() with rejection sampling, supports configurable character sets and lengths, and offers a passphrase mode based on the EFF wordlist. No data is transmitted to any server.
For server-side generation (Node.js):
import { randomBytes } from "crypto";
function generatePassword(length: number, charset: string): string {
const bytes = randomBytes(length * 4); // generous buffer
let result = "";
for (let i = 0; i < bytes.length && result.length < length; i++) {
const value = bytes[i];
if (value < Math.floor(256 / charset.length) * charset.length) {
result += charset[value % charset.length];
}
}
return result;
}
Summary
The most important principle in password generation is using a CSPRNG — crypto.getRandomValues() in the browser, crypto.randomBytes() in Node.js, or the equivalent system call in other environments. Combined with sufficient length (16+ characters) and a broad character set, this produces passwords that are computationally infeasible to brute-force, even given unlimited hardware.
