Statistical PRNG
xoshiro256**, PCG, Mersenne Twister. Fast, tiny state, excellent distribution. Not safe for secrets: the internal state is recoverable from enough output.
A small reference toolkit for random number generation — the differences between statistical and cryptographic generators, how to seed them correctly, and how to reproduce a sequence when you need to.
A pseudo-random number generator (PRNG) is a deterministic algorithm that expands a finite seed into a longer stream of bytes that behaves randomly. Determinism is the whole point: the same seed always produces the same stream, which is what makes Monte Carlo runs, fuzzing corpora and procedural content reproducible.
Determinism is also the trap. Two generators can both look random under a battery of statistical tests while only one of them is safe to derive a key from. Choosing the wrong one is a recurring source of real vulnerabilities, so the distinction is worth being precise about.
xoshiro256**, PCG, Mersenne Twister. Fast, tiny state, excellent distribution. Not safe for secrets: the internal state is recoverable from enough output.
ChaCha20, HMAC-DRBG. Built to be unpredictable even given the algorithm and
previous output. This is what crypto.getRandomValues() gives you.
Only the seed needs real entropy. Hardware sources feed the CSPRNG once; the stream from that point is cryptographic.
Everything below runs in your browser via
crypto.getRandomValues(). Nothing is sent to a server, nothing is logged.
press generate
| Family | State | Speed | Seekable | Use for secrets |
|---|---|---|---|---|
xoshiro256** | 256 bit | very fast | no | no |
PCG64 | 128 bit | fast | yes | no |
MT19937 | 19937 bit | fast | via init | no |
ChaCha20 | 256 bit | fast | yes (counter) | yes |
HMAC-DRBG | varies | moderate | no | yes |
"Seekable" means the generator can jump to an arbitrary position in the stream, which is useful for parallel workers each consuming an independent slice.
Two primitives cover most needs. Both are seeded from the platform CSPRNG.
// random bytes, the thing you almost always want const bytes = randomBytes(32); // → Uint8Array(32) // unbiased integer in [min, max] // rejection sampling, no modulo bias const n = randomInt(1, 100);
Modulo bias is the classic mistake: taking % range from a full-width value
makes the low residues slightly more likely. The implementation here rejects out-of-range
draws instead.