Why Math.random isn't safe for secrets
An ordinary random number generator is built for speed and spread, like shuffling a deck or placing enemies in a game. Its next output follows from its internal state, so someone who sees enough outputs can often work out the state and predict the rest. MDN is direct about it: Math.random() doesn't provide cryptographically secure random numbers, and nothing related to security should use it.
A CSPRNG closes that gap with two promises. Knowing the first k bits of its output doesn't help anyone guess the next bit. And if its internal state ever leaks, the numbers it made before that moment stay secret. It also has to start from a seed with real entropy, usually taken from the operating system's own randomness source.
Where to get secure random bytes
Every platform has one, and they're all one call:
- Browsers:
crypto.getRandomValues()fills an array of integers in place. It takes up to 65,536 bytes per call and also works on pages served without HTTPS. - Python: the
secretsmodule. Its documentation says therandommodule is meant for modeling and simulation, not security. - Node.js:
crypto.randomBytes()andcrypto.randomInt()in the built-in crypto module. - Linux:
getrandom(2)reads from the same source as/dev/urandomand waits only until the kernel has gathered enough entropy at boot.
These are pseudo random: a seeded algorithm, not a physical dice roll. The browser standard sets no minimum amount of entropy for the seed. It urges browsers to provide the best they can, from sources such as /dev/urandom.
For secrets, Python's documentation says 32 bytes of randomness is enough for typical use. That matches the smallest HS256 key RFC 7518 allows, 256 bits, and it takes 43 Base64url characters to write down.
Turning random bits into a password
A password generator has to map random numbers onto a character set, and the obvious way has a flaw. A random byte has 256 values. They don't divide evenly into 91 characters. Taking byte % 91 gives the first 74 characters three chances in 256 and every other character only two. Those first characters come up 50% more often, and that skew lowers the password's entropy.
The Password Generator avoids most of it by reading a 32-bit value for each character. That gives the characters a huge number of values to share, so the skew shrinks to about one part in 47 million. A careful generator either uses a wide value like that or throws away values from the uneven tail and draws again.
Keys and tokens need the same source
Anything an attacker must not guess should come from a CSPRNG: passwords, API keys, session IDs, salts and signing secrets. A JWT secret is a good example. RFC 8725 says a password a person can memorize must not be used directly as an HS256 key, because anyone holding one token can test guesses offline. Generate the secret from random bytes instead. The HS256 secret length guide shows what size to use.