Copynix
Security7 min read

How to Generate Cryptographically Secure Passwords: A Technical Deep Dive

Math.random() is not cryptographically secure. Learn how browser-native crypto.getRandomValues() works, how password entropy is calculated, and what makes a password generation algorithm truly safe.

T

Nguyễn Văn Thương

Founder, Copynix

passwordsecuritycryptography

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:

// 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:

LengthLowercase only (26)Lower+Upper+Digits (62)Full ASCII (95)
837.6 bits47.6 bits52.6 bits
1256.4 bits71.5 bits78.9 bits
1675.2 bits95.3 bits105.2 bits
2094.0 bits119.1 bits131.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.

Free tool

Try it yourself — Password Generator

Runs entirely in your browser. No signup, no data sent to servers.

Open Password Generator
T

Nguyễn Văn Thương

Founder & Developer · Copynix

Software developer focused on web security and developer tooling. Built Copynix to provide privacy-respecting browser tools that never send your data to a server.

More about the author →

Keep Reading

All articles →
All articlesBrowse tools →