Click "generate" on any password tool and a string like t9!Kv$2mXq#L appears in a millisecond. But where did those characters come from? That question matters, because if an attacker can rerun the same process, the password's apparent complexity is worthless.

Here's the tricky part: a trustworthy generator and a dangerous one look identical from the outside. Both produce gibberish that seems equally random. What separates them is the machinery underneath - A cryptographically secure pseudorandom number generator - And the care taken in mapping its raw bytes onto characters. Both parts have failed in famous, instructive ways.

Two kinds of "random"

Quick summary: ordinary PRNGs are for games and simulations; only a CSPRNG is safe for secrets.

Computers are deterministic - They follow exact instructions - So all software randomness is manufactured. But it's manufactured to two very different standards.

A PRNG (pseudorandom number generator) - Like C's rand(), Python's random module, or JavaScript's Math.random() - Expands a small seed into a long, statistically well-behaved stream. That's ideal for simulations and games: it's fast, and it repeats on purpose.

That repeatability is exactly what makes it fatal for security. V8's Math.random(), for instance, is a 128-bit xorshift generator with no cryptographic hardening. Researchers have repeatedly shown that watching a handful of outputs lets you rebuild its state and predict every future value.

It gets worse. If a tool seeds a PRNG with the clock, an attacker who roughly knows when you generated a password can replay every plausible seed. From there, they can list every password the tool could have produced that day.

A CSPRNG (cryptographically secure PRNG) makes a much stronger promise. Even an attacker who has seen huge amounts of output cannot predict the next bit or recover earlier bits - Not unless they can break the underlying cryptography.

Modern CSPRNGs are built from primitives like ChaCha20 or AES in counter mode (the same cipher family our AES-256 tool demonstrates). They are continually reseeded with fresh unpredictability from the physical world.

PRNG (Math.random) CSPRNG (crypto.getRandomValues)
Seeded from Small value, often time-derived OS entropy pool (hardware events)
Predictable from outputs? Yes - State recoverable No, barring a cipher break
Reproducible on demand Yes (a feature) Never
Speed Very fast Fast enough for anything
Fit for passwords/keys Never Yes - The only acceptable choice

Where the unpredictability comes from

A CSPRNG needs input that nobody can know. So operating systems run an entropy pool fed by physical jitter - Tiny, messy timing measurements such as:

  • Nanosecond-scale timing of keystrokes and mouse movement
  • Disk and network interrupts
  • Clock drift between separate oscillators
  • On modern CPUs, a hardware noise source (Intel's RDRAND/RDSEED draws from thermal noise in the silicon)

These measurements are hashed together and used to key the CSPRNG, which then stretches them into unlimited output. Linux exposes this via /dev/urandom and the getrandom() syscall. macOS and Windows run their own kernel generators.

Applications should never invent their own layer on top. The browser's Web Crypto API (crypto.getRandomValues()) hands any web page output from the OS generator directly.

That's why a well-built browser-based password generator can be exactly as trustworthy as a native app. The random bytes come from the same kernel machinery, and generation happens locally on your device - Nothing is sent over the network.

The strength of a generated password has nothing to do with how random it looks and everything to do with whether anyone else could rerun the process. "Unguessable" is a property of the source, not the string.

From random bytes to characters - The subtle bug

Once a generator has secure random bytes, it must map them onto a character set. Here lurks the classic mistake: modulo bias.

Suppose your character set has 62 entries and you compute byte % 62. A byte has 256 possible values, and 256 = 4×62 + 8. So characters 0–7 of your alphabet get five ways to be chosen, while the rest get four. The output is measurably skewed. A cracking tool that knows the generator can try the favored characters first and shave real entropy off its search.

The correct fix is rejection sampling. Draw a byte; if it falls in the uneven remainder zone (248–255 in this example), throw it away and draw again. Every character then has exactly equal odds. Only then does the entropy math - The L × log₂(N) formula from password entropy explained - Truly hold.

The same discipline applies to word-based generation. Picking uniformly from a 7,776-word Diceware list requires the identical technique. That's one reason dice work so naturally for passphrases - Five dice rolls are a perfect uniform draw from 7,776.

Details that separate careful generators from careless ones:

  • Uniform selection via rejection sampling, never plain modulo.
  • Independent draws - Each character sampled fresh, no shuffling a fixed template.
  • "At least one digit/symbol" done right - Naive implementations that force character classes into fixed positions reduce entropy. Correct ones generate-and-retest, or place required classes at random positions using the CSPRNG itself.
  • Local generation, no transmission or logging - A password that traveled through someone's server logs has already leaked.
  • Excluding ambiguous characters (O/0, l/1) only as an option, since shrinking the alphabet slightly lowers per-character entropy.

When randomness fails: three cautionary tales

Netscape, 1995. The browser's SSL keys were seeded from the time of day and process IDs - Values an attacker could closely guess. Two Berkeley grad students reversed it and could break a session's encryption in minutes. The lesson: a strong cipher keyed by weak randomness is a weak cipher.

Debian OpenSSL, 2006–2008. A packaging patch accidentally removed nearly all entropy feeding OpenSSL's generator. That left the process ID - Just 32,768 possibilities - As the only seed. For two years, keys generated on Debian and Ubuntu systems came from one tiny, fully listable set. Attackers could simply pregenerate them all.

Android SecureRandom, 2013. A flaw in Android's Java CSPRNG meant some apps got repeated or predictable values. Bitcoin wallets generated with it reused signing nonces, letting thieves compute private keys and drain funds from real wallets.

None of these outputs looked wrong. The bugs were found by analysis, not by staring at the gibberish. That's the standing argument for using audited OS primitives rather than clever homegrown randomness.

The rule applies to every security tool that consumes random bytes. Unique tokens like UUIDv4 fill 122 bits from the same well. Even one-time codes are only as good as the secret behind them - A TOTP generator's six digits rotate every 30 seconds, but the base32 secret seeding them had to be born from a CSPRNG once, too.

Quick audit for any generator you're considering: does it run locally in your browser or device? Does its page or source mention `crypto.getRandomValues`, `/dev/urandom`, or `SecureRandom` rather than `Math.random` or `rand()`? Can you disconnect from the network and still generate? "Yes" to all three is the baseline.

Why generated beats human-invented, every time

The practical payoff of all this machinery is simple. A CSPRNG samples uniformly from the entire space. Humans sample from the tiny, well-mapped corner of it filled with words, dates, and keyboard patterns.

Breach analyses show the same few thousand strings dominating human choices year after year - The roll call in the most common passwords barely changes. Meanwhile, picture a uniform draw of 16 characters from 95 symbols. The odds it collides with anything an attacker would try early: about 1 in 10³¹. The generator doesn't have better taste than you. It has no taste at all, and that's the entire point.

FAQ

Is it safe to use an online password generator?

It can be - If generation happens client-side with the Web Crypto API and the password never leaves your device. The random bytes then come from your own operating system's CSPRNG, identical in quality to a native app. Avoid any tool that generates server-side or transmits results. You can verify local generation by loading the page, going offline, and generating again.

What's wrong with Math.random() for passwords?

It's an ordinary PRNG (xorshift128+ in V8) designed for speed and statistical smoothness, not secrecy. Its internal state can be rebuilt from a small number of observed outputs, which makes future values predictable. And its seeding was never meant to resist attackers. Every serious platform provides a CSPRNG precisely because functions like this must not produce secrets.

Are hardware random number generators better than software CSPRNGs?

They solve different parts of the problem. Hardware sources (thermal noise, RDSEED) provide raw physical unpredictability. But their output is filtered and fed into a software CSPRNG rather than used directly - Raw hardware noise can be biased or, in principle, backdoored. Best practice, and what your OS already does, is mixing hardware entropy with timing entropy into an audited CSPRNG like ChaCha20-based /dev/urandom.

Can two people ever get the same generated password?

With a working CSPRNG, the probability is negligible. Two independent draws of a 16-character password from 95 symbols collide with odds around 1 in 10³¹. Real-world collisions - Like the Debian OpenSSL key duplicates - Are always a symptom of broken seeding, not bad luck. That's why the incident is taught as the canonical RNG failure.

Do I need to add my own keyboard mashing to make a password "more random"?

No. Mashing feels random, but it follows strong hand-alternation and home-row patterns that cracking tools model explicitly. The OS entropy pool already incorporates far finer-grained unpredictability (nanosecond event timing) than deliberate typing can provide. Trust the uniform draw - If you want more strength, increase the length setting instead.