Where randomness comes from
crypto.getRandomValues asks the operating system's CSPRNG (getrandom() on Linux, SecRandomCopyBytes on macOS, BCryptGenRandom on Windows), which is seeded from hardware events and, on modern CPUs, the RDRAND instruction. It is unpredictable in both directions: knowing a million outputs tells you nothing about the next one. Math.random() is not; it is a fast PRNG with a small internal state, fine for games and never for secrets.
How many bytes
| Purpose | Bytes | Why |
|---|---|---|
| Session ID, CSRF token | 16–32 | 128 bits is beyond brute force; 32 bytes is the common default. |
| API key / secret | 32 | 256 bits; hashed with SHA-256 for storage, like a password but with no need for slow hashing because it is random. |
| HMAC / JWT signing key | 32–64 | Match the hash output: 32 for SHA-256, 64 for SHA-512. |
| AES key | 16 / 32 | AES-128 or AES-256. |
| Salt | 16 | Uniqueness is what matters; 128 bits guarantees it. |
| Password reset token in a URL | 16–24 | Base64URL keeps it short; store its hash, expire it quickly. |
Random integers without bias
Taking random_byte % 6 for a die is subtly wrong: 256 is not divisible by 6, so 1–4 come up slightly more often than 5–6. The correct method draws a value, rejects it if it falls in the leftover range at the top, and tries again; that is what this generator does and what secrets.randbelow, crypto.randomInt and rand.Intn do.
Prefixed keys
Prefixes such as sk_live_, ghp_ or AKIA add no security but make keys recognisable in logs and let secret scanners (GitHub's, gitleaks) find leaked ones. Pick a prefix unique to your service and keep the random part at 32 bytes.
Questions
Is this as good as openssl rand or /dev/urandom?
Yes: the browser calls the same operating-system generator those tools read. The output is suitable for keys and secrets. If you need it in a shell anyway: openssl rand -hex 32, or head -c 32 /dev/urandom | base64.
Should I store API keys hashed?
Yes, with a fast hash such as SHA-256; because the key is random, a slow password hash is unnecessary. Show the key once at creation and keep only the hash, like GitHub and Stripe do.
What is Base64URL?
Base64 with - and _ instead of + and /, usually without padding, so the value can sit in a URL or a cookie without escaping. JWTs and most modern tokens use it.
Why not just use a UUID as a token?
A UUIDv4 has 122 random bits, which is enough, but its fixed format advertises what it is and wastes characters on hyphens. For secrets, raw random bytes in Base64URL are shorter and stronger. UUIDs are for identifiers, not secrets.