How to generate a random string (correctly)
The right call in eight languages, and the popular ones that quietly aren't random.
Or just take one from here
Get this one thing right
Every language ships two random number generators. One is fast, seeded predictably, and built for simulations and shuffling. The other is cryptographically secure. They are often one character apart in the API, and the fast one is usually the one that comes up first in search results. If a value is a secret — a token, a key, a password, a session id, a reset link — it must come from the second kind. Everything below uses the right one.
The correct call, by language
bash: openssl rand -hex 32
bash: head -c 32 /dev/urandom | base64 — /dev/urandom is the OS CSPRNG and is the correct choice on Linux; the old advice to prefer /dev/random is obsolete.
Python: import secrets; secrets.token_hex(32) — secrets, never random.
Node: require('crypto').randomBytes(32).toString('hex')
Browser: crypto.getRandomValues(new Uint8Array(32))
Go: b := make([]byte, 32); rand.Read(b) — from crypto/rand, never math/rand.
PHP: bin2hex(random_bytes(32))
Java: new SecureRandom().nextBytes(b)
Ruby: SecureRandom.hex(32)
Rust: rand::rngs::OsRng.fill_bytes(&mut b)
PowerShell: [System.Security.Cryptography.RandomNumberGenerator]::GetBytes(32)
The ways that look right and aren't
Math.random() — the single most copied mistake in JavaScript. It is not seeded securely and its output is predictable from previous outputs. Every Math.random().toString(36).substring(2) snippet on the internet is generating guessable "secrets". Use crypto.getRandomValues().
$RANDOM in bash — returns a number from 0 to 32767. That is 15 bits. An attacker enumerates the entire space instantly, and it is seeded from the PID and the clock.
random.choice() in Python — the Mersenne Twister. Brilliant for simulations, and its entire internal state can be reconstructed from 624 consecutive outputs. Python's own docs say to use secrets for security.
math/rand in Go — one import line away from crypto/rand and completely unsuitable. This is the easiest mistake on this page to make and the hardest to spot in review.
uniqid() in PHP — derived from the current time. Two calls in the same microsecond collide, and any call is guessable if you know roughly when it happened.
A UUID as a secret — a v4 UUID is fine, since it is 122 random bits from a CSPRNG. A v1 or v7 UUID is not: those embed a timestamp and are partly predictable by construction.
The subtle one: modulo bias
Even with a correct CSPRNG, this is wrong:
chars[randomByte % chars.length]
If chars.length does not divide 256, the first few characters of the alphabet come up more often than the rest. With a 62-character alphabet, 256 = 4×62 + 8, so the first eight characters are about 25% more likely than the others. It is invisible in the output and it measurably shrinks the keyspace. The fix is rejection sampling: draw, discard anything in the uneven tail, draw again. Python's secrets.choice does this. Go's rand.Int does this. Every generator on this site does this — including the Django key, whose 50-character alphabet is a textbook case of the problem.
What's wrong with Math.random()?
It is not seeded securely and its output is predictable from previous outputs — it was built for simulations, not secrets. Every Math.random().toString(36).substring(2) snippet on the internet is generating guessable values. Use crypto.getRandomValues() instead; it is in every browser and in Node.
Why is $RANDOM in bash not enough?
It returns a number from 0 to 32767 — 15 bits — and it is seeded from the process id and the clock. An attacker enumerates the whole space instantly. Use openssl rand -hex 32 or read from /dev/urandom.
What is modulo bias?
Taking a random byte and reducing it modulo your alphabet length, when that length does not divide 256. With a 62-character alphabet, 256 = 4×62 + 8, so the first eight characters come up about 25% more often than the rest. It is invisible in the output and it shrinks your keyspace. The fix is rejection sampling: draw, discard the uneven tail, draw again.
Can I use a UUID as a random secret?
A v4 UUID, yes — it is 122 random bits from a CSPRNG. A v1 or v7 UUID, no: those embed a timestamp and are partly predictable by construction. If you want a secret rather than an identifier, a 32-byte hex or base64 value is a clearer choice.
Nothing leaves your browser
- Don't take our word for it — turn off your wifi. Every generator on this site keeps working with the network disconnected. That's the whole proof, and it takes five seconds.
- Every value comes from
crypto.getRandomValues()— the CSPRNG built into your browser, neverMath.random(). - Generated secrets are never transmitted, logged or stored: no server-side generation, no cookies, no localStorage.
- Verify it yourself in the network tab: after loading, the page only talks to our self-hosted, cookie-less analytics — which counts page views and which generator type gets copied, never any value.
- Strict Content-Security-Policy; no third-party script origins.