Security & Privacy

Generating a random string that is actually random

Two generators produce strings that look equally random. One of them lets an attacker predict every future value after seeing a few.

4 minute read

You need a token: an API key, a password reset link, a share URL, a filename that nobody should be able to guess. You generate a random string, it looks like nonsense, and you ship it.

Whether that was safe depends entirely on which generator produced it, and the two kinds are indistinguishable by eye.

Two different meanings of random

A pseudo-random number generator is a deterministic algorithm. It holds an internal state, and each call transforms that state and returns part of it. The output passes statistical tests for randomness, which is what it was designed for: simulations, shuffling a playlist, jittering an animation.

It is not designed to be unpredictable to an adversary. Given enough consecutive outputs, the internal state can be recovered, and once it is, every future value is known. This has been demonstrated against Math.random in every major JavaScript engine.

A cryptographically secure PRNG is built so that observing output tells you nothing useful about the state or about future output. It draws from the operating system's entropy pool, which collects unpredictable physical signals: interrupt timings, device noise, hardware random instructions.

In a browser that is crypto.getRandomValues(). On a server it is /dev/urandom or the platform equivalent.

Entropy is the number that matters

Entropy measures how many guesses an attacker needs. For a random string:

bits = length x log2(alphabet size)
Alphabet Bits per character Length for 128 bits
Digits (10) 3.32 39
Lowercase (26) 4.70 28
Alphanumeric (62) 5.95 22
Base64url (64) 6.00 22
Full ASCII printable (94) 6.55 20

The useful thresholds: 128 bits is the standard target for anything security-relevant, and is far beyond brute force. Around 64 bits is adequate for a short-lived value that also has rate limiting behind it. Below about 40 bits is guessable by anyone motivated.

Note what this says about length versus alphabet. A 16-character alphanumeric token is 95 bits. A 22-character one is 131. Adding six characters did more than any amount of adding exotic symbols would, which is the same lesson as what makes a password strong.

The modulo bias

There is a subtle failure in how random bytes get turned into characters.

The obvious approach is to take a random byte, 0 to 255, and take it modulo the alphabet size. With a 62-character alphabet, 256 is not divisible by 62: values 0 to 61 can be produced by five different bytes, while 62 to 65 can be produced by only four. The first few characters of your alphabet come up about 25% more often than the rest.

For a shuffled list this is invisible. For a token it reduces entropy, and a generator that is systematically biased is a generator an attacker can exploit.

The correct approach is rejection sampling: discard bytes that fall in the uneven tail and draw again. A random string generator that gets this right produces a uniform distribution; one that does not produces something subtly weaker than its length suggests.

Choosing what to generate

A random string when you control both ends and want a specific length and alphabet. Avoid ambiguous characters (0/O, 1/l/I) if a human will ever retype it.

A UUID when you want a standard format other systems will recognise. A v4 UUID has 122 random bits, which is plenty. Note that it is 36 characters to carry 122 bits, which is less dense than base64url, and that a v7 UUID deliberately embeds a timestamp and is therefore partly predictable by design.

A passphrase when a human has to remember or read it aloud.

A password when it goes into a password manager and nobody types it.

Two things a random string is not

It is not a hash. A hash is deterministic: the same input always gives the same output. That is the point of a checksum and the opposite of the point of a token.

And it is not secret once it is in a URL. Values in a URL end up in browser history, server access logs, Referer headers and anything that scans links in messages. An unguessable URL is a reasonable pattern for a share link with an expiry; it is not a substitute for authentication on anything that matters.

Generating random values is one of the few things a browser does better than a server for your purposes, because crypto.getRandomValues() runs locally and the value never crosses a network. A token generated on someone else's server has, by definition, been seen by someone else before it reached you.

Further reading

Do it hereRandom String GeneratorGenerate random strings with custom length, charset and output countOpen

Written for NextTool. Every statement about a tool is checked against that tool's code, and corrections are dated above. How guides are written

randomnesstokensentropy