Skip to main content

How to Generate a Strong API Key: Entropy, Prefixes, and Local Privacy

A practical guide to generating secure API keys: how much entropy you actually need, why prefixes like sk_live_ carry no secrecy, and how to mint keys 100% in your browser.

Published By 李雷
#api-key #security #developer-tools #cryptography

How to Generate a Strong API Key: Entropy, Prefixes, and Local Privacy

Every backend that authenticates a caller eventually needs an API key. It looks like a small problem until you ship one made with Math.random(), watch it leak into a public commit, and realize you can't tell whether the leaked key guards production money or a sandbox. A good key is not just a long string. It is the right amount of randomness, from the right source, wearing a label that tells your tooling what it is. This guide walks through all three, then generates a real one.

What makes an API key actually strong

An API key has exactly one job: be unguessable. That means the part that matters is the random body, and the only thing that makes it unguessable is entropy measured in bits. A 128-bit body has 2^128 possible values, roughly 3.4 × 10^38. Even at a wildly optimistic trillion guesses per second, exhausting that space takes longer than the age of the universe by an enormous margin. That is why 128 bits is the floor for a credential, and why 160 to 256 bits is the sane target for anything guarding real money or user data.

The catch is that bits of entropy depend on your character set, not on the visible length. With base62 (the digits plus upper and lower case letters), each character is worth log2(62) ≈ 5.95 bits. With hex, each character is only 4 bits because there are just 16 possibilities. So a 32-character hex string gives you exactly 128 bits, while a 22-character base62 string already reaches about 131 bits. Length alone tells you nothing; length times bits-per-character tells you everything.

Use a CSPRNG, never Math.random()

Here is the part that quietly ruins otherwise-careful systems. Math.random() is a non-cryptographic pseudo-random generator. Its internal state can be recovered from a handful of outputs, which means an attacker who sees one of your keys can predict the next one. No amount of length saves you, because the weakness is in the source, not the size.

The correct source in a browser is the Web Crypto API, specifically crypto.getRandomValues, which is a cryptographically secure generator seeded by the operating system. It is the same class of randomness a server-side CSPRNG would use. The API Key Generator uses it exclusively and never falls back to Math.random(). If you write your own generator, that is the single non-negotiable rule: the byte source must be a CSPRNG.

Prefixes are labels, not secrets

Stripe popularised the prefix convention, and it is worth copying. A prefix like sk_live_ packs two facts into the key itself: sk means secret key (as opposed to pk, a publishable key), and live versus test marks the environment. The payoff is operational. When a key shows up in a log line or a GitHub push, an automated scanner reads the prefix and instantly knows whether to rotate a production credential or shrug off a harmless sandbox token.

But a prefix is routing metadata, never a secret. It is completely predictable, so it contributes exactly zero bits of entropy. This trips people up constantly: a four-character random body hidden behind a long, official-looking prefix is still trivially brute-forced. Size the random body, not the label. A well-built generator reflects this by measuring strength on the random body only and ignoring the prefix entirely when it reports entropy.

Why local generation is the privacy win

I have a personal rule about secret-generating tools: if the site can see the key, I don't use it. Most online generators mint the value on a server, which means a real credential briefly exists somewhere you don't control, and you are trusting a stranger's logging discipline with the keys to your production system.

Generating in the browser flips that. The key is built in memory on your own machine and is never sent anywhere, never logged, never written into a shareable URL. Your settings (prefix, length, charset, count) do ride in the URL query string so a shared link reproduces the configuration, but the keys themselves are deliberately kept out and re-randomise on every Generate click. A share link reveals what shape of key you make, never an actual secret. The honest caveat is your own device: generate on a machine you trust, and treat the output as a live credential the instant it exists.

A worked example

Say I'm wiring up a webhook receiver and need a bearer token for the Authorization header. I set the prefix to sk_live_, the charset to base62, and the length to 32. The strength panel does the math live: 32 base62 characters × 5.95 bits ≈ 190 bits of entropy in the random body, far past any brute-force threat. The output reads like:

sk_live_4f9Kp2QmZ7vR3nXdL8sJ1aB6cW0tYgH

The sk_live_ part is the readable label; everything after it is the 32-character random body the strength panel actually scored. I copy it once, store only its salted hash on my server, and hand the plaintext to the client exactly once. Need a token that drops into a magic-link URL instead? Switch the charset to base64url (it swaps + and / for - and _) and set the length to 43, which lands you at 256 bits, the entropy of a full SHA-256, in a string that survives a query parameter untouched.

Picking the right tool for the job

One last distinction worth nailing down: an API key is not a UUID. A UUID is a 128-bit identifier with a fixed format and version bits, designed to be unique and perfectly fine to expose in URLs and logs. An API key is a credential whose entire purpose is to be unguessable, so it wants maximum entropy and must be hashed at rest. Use a UUID to name a row in your database; use an API key to authenticate the caller hitting that row. If what you actually want is a short, sortable, unique identifier rather than a secret, reach for a NanoID or ULID generator instead.

And the storage rule that ties it all together: treat the key like a password. Keep only a salted hash or HMAC, compare hashes on each request, and show the user the full plaintext exactly once at creation. If your database ever leaks and you stored raw keys, every one of them is immediately usable. Generate it strong, label it clearly, hash it at rest, and the leak becomes a rotation chore instead of a breach.


Made by Toolora · Updated 2026-06-13