Methodology

The algorithm behind every value on this site, and the test results that verify it. Of the fifteen generators we audited on the first page of Google, none publishes either.

Where the randomness comes from

Every value is produced from crypto.getRandomValues(), the browser's interface to the operating system's cryptographic random number generator. On Linux that is the getrandom syscall, on Windows BCryptGenRandom, on Apple platforms SecRandomCopyBytes. All three draw from a kernel pool seeded from hardware sources.

There is no fallback. If a browser cannot provide a secure source, the tool shows an error and refuses to generate. A silent downgrade to a weaker source is the worst possible failure for a tool people use to make real secrets, and it is exactly what several generators do.

The Web Crypto specification refuses a single request larger than 65,536 bytes, so bulk generation chunks around that limit. It also draws into a pooled buffer rather than calling the API once per character, which is why ten thousand strings take milliseconds rather than seconds. The values are identical either way; only the number of syscalls changes.

Removing modulo bias

Turning random bits into characters is where most generators go wrong. The obvious approach is to take the remainder of a random number divided by the alphabet size. It does not divide evenly, so some characters come up more often than others.

Concretely, mapping a single byte onto a 62-character alphabet: 256 = 4 x 62 + 8. Values 0 to 247 cover the alphabet four times each. Values 248 to 255 cover the first eight characters a fifth time. Those eight are drawn with probability 5/256 and the other fifty-four with 4/256, so they are 1.25 times more likely. Over a sixteen-character string that is 4.418 bits of min-entropy lost against the 95.267 the tool would claim.

We remove it with rejection sampling. Draws are taken as 32-bit values and compared against the largest exact multiple of the alphabet size:

limit = Math.floor(2 ** 32 / n) * n;
do { v = pool.next(); } while (v >= limit);
index = v % n;

Values at or above the limit are discarded rather than folded in, so every accepted value maps onto exactly the same number of preimages. The cost is negligible: at 62 characters, exactly four values out of 2^32 are rejected, so a redraw happens about once per billion draws.

Character classes without positional bias

"Must contain at least one uppercase, one digit and one symbol" is usually implemented by generating a string and then overwriting fixed positions. That leaves a visible positional pattern. Shuffling afterwards removes the pattern only if the shuffle is itself unbiased, which the common naive version is not, and even a perfect shuffle leaves some valid strings more likely than others.

This tool uses whole-string rejection instead: it generates a complete candidate, tests it against every required class, and discards the whole thing on failure. Every valid string is produced with exactly equal probability, by construction. For an eight-character password over all 94 printable characters with four required classes, the acceptance rate means about 2.17 attempts per output. At sixteen characters it falls to 1.215.

Where a shuffle is needed the implementation is Fisher-Yates in its downward form, with indices from the same unbiased sampler. The naive variant that swaps each position with a random index over the whole range has n^n execution paths mapping onto n! permutations, and n! never divides n^n for n greater than 2, so some orderings must come up more often.

Entropy, reported honestly

The figure every generator prints is length multiplied by the log base two of the alphabet size. That is correct only when each character is independent and uniform. Requiring a character class, excluding look-alikes, or forbidding repeats all make the real entropy lower than that formula gives.

This tool computes the true value: the entropy of a uniform choice over the set of valid strings is the log base two of how many valid strings there are, so it counts them by inclusion-exclusion over the required-class partition. The same expression handles the no-repeat case using falling factorials. Below length 256 the count is computed exactly with arbitrary precision integers, because that is where floating-point cancellation would bite; above it the acceptance probability is so close to one that double precision is exact enough.

When a constraint costs entropy, the display shows both the real figure and the unconstrained one, so the cost is visible rather than hidden.

Test results

Generated 25,000 strings of 20 characters, which is 500,000 characters over an alphabet of 62. A test fails below p = 0.001. These numbers are produced by a committed script and regenerated on every release.

Our sampler

Test Statistic p Result
Character frequency 55.55 on 61 df 6.73e-1 pass
Positional bias 1212.68 on 1220 df 5.54e-1 pass
Adjacent repeats z = 0.469 6.39e-1 pass
Serial pairs 3666.18 on 3843 df 9.79e-1 pass

The control, which must fail

A test suite that passes everything proves nothing. So the identical battery is run against a deliberately biased implementation: the naive byte % 62 mapping. If it passed, the clean result above would mean nothing either.

Test Statistic p Detected?
Character frequency 3345.40 on 61 df < 1e-300 caught
Positional bias 4549.29 on 1220 df < 1e-300 caught
Adjacent repeats z = 0.446 6.56e-1 not caught
Serial pairs 10069.33 on 3843 df < 1e-300 caught

The adjacent-repeat test does not catch modulo bias, and that is expected rather than a defect. It measures how often two neighbouring characters are equal, which stays close to 1 in 62 even when the marginal frequencies are skewed. It is included because it catches a different failure, a generator whose successive draws are correlated.

The statistics implementation, checked against published values

The chi-square and normal tail probabilities are computed from an incomplete gamma function rather than a lookup table, so that implementation is itself verified against known critical values before any result above is trusted.

Check Computed Published
chi2 crit df=1 at p=0.05 0.050000 0.05 ok
chi2 crit df=10 at p=0.05 0.050000 0.05 ok
chi2 crit df=61 at p=0.05 0.050003 0.05 ok
chi2 crit df=61 at p=0.01 0.010001 0.01 ok
normal two-sided at z=1.959964 0.050000 0.05 ok
normal two-sided at z=2.575829 0.010000 0.01 ok

Speed

Batch Total Per string
100 x 32 characters 0.26 ms 2.63 us
1,000 x 32 characters 5.6 ms 5.60 us
10,000 x 32 characters 16.84 ms 1.68 us
10,000 x 64 characters 25.82 ms 2.58 us

Verified. Our sampler passes every test and the biased control is detected. Both halves of that sentence matter. Generated 2026-09-13.

What we do not claim

We do not claim better entropy than a hardware source. RANDOM.ORG samples atmospheric noise and that is genuinely a different and in some senses superior source; saying otherwise would be easy to disprove. What we claim instead is that our output is locally generated, that the mapping from bits to characters is provably uniform, and that both are auditable.

We also do not claim that every other generator is broken. Of fifteen audited, five use Math.random() in generator logic, one uses a secure source with a biased mapping, two are textbook-correct, and seven generate server-side with legitimate sources. Correct rejection sampling is table stakes, not a differentiator. What none of them does is publish the algorithm and the test results.

Nothing you generate is transmitted, logged or stored. You do not have to take that on trust either: open your browser devtools, switch to the Network tab, and press Generate. Nothing appears, because there is no server to send it to.