Secure Random String Generation for Developers
Why <code>Math.random()</code> is not enough for API keys and tokens, how cryptographically secure random number generators work, and how to choose the right length and character set.
PRNG vs CSPRNG
A Pseudo-Random Number Generator (PRNG) like Math.random() produces numbers that appear random but are derived from a deterministic seed. Given enough output, an attacker can predict future values. This makes PRNGs completely unsuitable for security-sensitive strings.
A Cryptographically Secure PRNG (CSPRNG) sources entropy from the operating system (hardware events, interrupt timings) and is designed to be computationally infeasible to predict. In the browser this is crypto.getRandomValues(); in Node.js it is crypto.randomBytes().
Browser CSPRNG
// Fill a typed array with secure random bytes
const bytes = new Uint8Array(32);
crypto.getRandomValues(bytes);Building a Random String from Random Bytes
Once you have random bytes, map them to a character set:
function randomString(length, charset) {
const bytes = new Uint8Array(length);
crypto.getRandomValues(bytes);
return Array.from(bytes)
.map(b => charset[b % charset.length])
.join('');
}
const ALPHA = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
const token = randomString(32, ALPHA);Note: using b % charset.length introduces a tiny modulo bias when the charset length does not evenly divide 256. For most use cases this bias is negligible. For high-security applications, use rejection sampling: discard any byte ≥ floor(256 / charset.length) * charset.length.
How Long Should a Random String Be?
Security comes from entropy — the number of possible values. An N-character string over an alphabet of size A has AN possible values. The entropy in bits is N × log₂(A).
- 32 chars, alphanumeric (62): ~190 bits — suitable for API keys and session tokens
- 64 chars, alphanumeric: ~381 bits — suitable for long-lived secrets
- 16 chars, hex (16): 64 bits — minimum for low-value nonces
- 128 bits is the commonly accepted minimum for secrets that need to withstand brute force
Character Set Trade-offs
Alphanumeric (A-Z, a-z, 0-9)
62 characters, URL-safe, copy-paste friendly, readable. Best for API keys and tokens that appear in config files or headers.
Hex (0-9, a-f)
16 characters — only 4 bits per character. Requires longer strings for the same entropy but is universally safe in any context (URLs, JSON, headers, filenames).
Base64url
64 characters (A-Z, a-z, 0-9, -, _). URL-safe variant of Base64 without padding. Common in JWT and OAuth tokens. 6 bits per character — good balance of compactness and safety.
Full printable ASCII
95 characters. Maximum density per character — good for passwords where humans need to type them. Avoid ambiguous characters (O, 0, l, 1) if readability matters.
Common Use Cases
- API keys: 32–48 alphanumeric chars with a vendor prefix (e.g.,
sk_live_...) - Session tokens: 32+ chars, stored server-side, invalidated on logout
- CSRF tokens: 32 chars, per-form, single-use
- Password reset links: 32+ chars, short TTL (15–60 min)
- Invite codes: 8–12 chars, can use smaller charset for readability
What to Avoid
- Math.random(): Predictable, never use for security
- Timestamps as tokens: Guessable; narrow search space
- UUIDs for secrets: UUID v4 has only 122 bits of randomness and v1 leaks the MAC address — better to use raw random bytes
- Short strings: Under 128 bits entropy is exploitable given enough attempts