← Blog
Security8 min

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