API keys explained: length, format, storage and rotation

Updated:

An API key is a long random string that a program sends with each request to show which application is calling. Unlike a password, nobody has to remember it, so it can be as long and as random as needed. Unlike a sign-in token, it usually lasts until someone revokes it. Google’s documentation sums up the risk: API keys are bearer credentials, so whoever holds one can use it.

This fourth part of the Security series covers the whole life of a key: how many random bits it needs, which format to write it in, why prefixes such as sk_live_ exist, how to send it, what the server should store, and how to rotate and revoke it.

API key, password or access token?

All three prove something to a server, which is why they are often confused. The difference is who makes them, how long they last and what the server keeps:

CredentialMade byLastsThe server keeps
Passworda personuntil the person changes ita slow hash (Argon2id, scrypt, bcrypt)
API keya random generatoruntil it is rotated or revokeda fast hash, such as SHA-256
Access token (OAuth, JWT)the sign-in server, after a loginminutes to hoursa signature to check, or a record of the token

An API key normally identifies an application or an integration rather than a person. Where a key would stand in for a person signing in, an access token from a proper sign-in flow is usually the better tool, as the previous part on JSON Web Tokens explains.

How long a key should be

What protects a key is the number of random bits in it, not its length in characters. Each bit doubles the number of possible keys: 128 random bits allow about 3.4 × 10³⁸ keys, so trying even a millionth of them at a billion guesses per second would take about 10¹⁶ years, nearly a million times the age of the universe. 128 bits is therefore a sound minimum, and 256 bits costs only a few more characters.

The bits must come from a cryptographic random generator. In a browser that is crypto.getRandomValues(); MDN warns that Math.random() does not provide cryptographically secure numbers and must not be used for anything related to security. Server languages have their own equivalents, such as the secrets module in Python or crypto.randomBytes() in Node.js.

GitHub raised its OAuth tokens from 160 to 178 bits of entropy when it changed their format in 2021. One case needs even more: a secret that signs JSON Web Tokens with HMAC. RFC 7518 requires a key at least as long as the hash output, so 256 bits for HS256, 384 for HS384 and 512 for HS512.

Formats: base62, hexadecimal or base64url

The random bits have to be written as text. The alphabet decides how many characters are needed and where the key can be pasted without trouble:

FormatCharactersBits per characterLength for 256 bits
base62a–z, A–Z, 0–95.9543
hex0–9, a–f464
base64urla–z, A–Z, 0–9, - and _643

Base62 has become common for keys because it is made only of letters and digits: nothing that a URL, a shell or a configuration file treats specially, and a double-click selects the whole key. Hexadecimal is longer but works where only 0–9 and a–f are accepted. Base64url, from RFC 4648, is the shortest, but its - can stop a double-click selection. Standard Base64 is a poor choice, because its + and / need escaping in URLs.

Writing base62 correctly takes care: a random byte has 256 values, and 256 is not a multiple of 62, so a plain byte % 62 makes the first eight characters slightly more likely. A good generator discards bytes from 248 upwards, as the nTools generator does.

Prefixes and secret scanning

Many providers start their keys with a fixed, readable prefix. Stripe’s secret keys begin with sk_live_ or sk_test_, its publishable keys with pk_ and its restricted keys with rk_. Since 2021, GitHub’s tokens begin with ghp_ for personal access tokens, gho_ for OAuth tokens and ghs_ for server-to-server tokens.

A prefix is not secret and adds no strength, but it does three useful jobs. A person can tell at a glance what a key is for and whether it is a test key. GitHub chose an underscore because it is not a Base64 character, so random strings such as hashes cannot look like a token by chance, and because a double-click selects the whole token. Above all, a prefix lets scanners find leaked keys: GitHub’s secret scanning searches the entire Git history of every branch, and when it finds a partner provider’s secret it notifies that provider, which can revoke the key.

GitHub also ends its tokens with a 32-bit CRC32 checksum written in six base62 characters. The checksum adds no security, but it lets a scanner rule out random strings offline, without asking the server whether the token exists.

Send it in a header, never in the URL

RFC 6750, which defines bearer tokens for OAuth 2.0, says clients should send them in the Authorization header and should not pass them in page URLs, because addresses end up in browser history and server logs. Google gives the same advice for its own API keys and offers a header for them. The same RFC requires TLS: a key sent over plain http can be read by anyone on the way.

What the server should store

The server does not need to keep the key itself, only a way to recognise it. Store a hash of the key, such as its SHA-256; when a request arrives, hash the key it carries and look that hash up. If the database leaks, the hashes cannot be used to call the API, and the keys cannot be recovered from them.

Passwords need a slow hash because, as OWASP’s Password Storage Cheat Sheet explains, a fast hash such as SHA-256 lets attackers try huge numbers of guesses quickly. That reasoning depends on passwords being guessable. A key with 256 random bits is not, so a single fast hash is enough, and it keeps every request quick.

Because only the hash is kept, the full key can be shown just once, when it is created. Stripe works this way for the live secret keys you create: the dashboard shows the key one time and cannot reveal it again. Keep the prefix and the last few characters in clear if people need to tell their keys apart.

Keeping keys out of code

Leaked keys are mostly found where developers put them for convenience. Stripe warns that bad actors continuously scan public repositories for API keys, and that even private repositories can be exposed. Its advice, echoed by Google and OWASP:

Rotation and revocation

Every key should be replaceable at short notice. Rotating means creating a new key, moving every application to it and then deleting the old one. Done periodically, it limits how long a stolen key keeps working and, as Stripe points out, it proves that you know where each key is used. Stripe’s dashboard keeps the old and the new key working together for up to seven days to make the switch smooth.

When a key is exposed — committed to a repository, pasted into a chat, printed in a log — revoke it at once, even if you are not sure anyone saw it. Deleting the commit is not enough: the key may already have been copied, and it stays in the Git history and in every clone. Where a system allows it, give keys an expiry date as well, so that a forgotten key stops working on its own.

Generate a key

The nTools API key generator makes keys in base62, hexadecimal or base64url, with 128 to 512 bits, an optional prefix and, if you want it, the SHA-256 hash to store on the server. It uses crypto.getRandomValues() and runs entirely in your browser: the keys are not sent anywhere.

API key generator

Frequently asked questions

Can I use a UUID as an API key?

A version 4 UUID has 122 random bits and a fixed, recognisable shape, and it was designed as an identifier, not a secret. It is better to generate a dedicated key with 128 bits or more.

Is a key generated in a browser safe?

Yes, if it comes from crypto.getRandomValues(), the cryptographic generator the browser uses for its own security, and the page sends it nowhere. The nTools generator works that way: no request leaves the page when you click Generate.

Should API keys expire?

Where the system supports it, yes: an expiry date limits the damage from a key that was forgotten or leaked unnoticed. Long-lived keys are common, which makes rotation and quick revocation all the more important.

I committed a key to Git. What now?

Revoke or rotate it first, then clean the repository. Rewriting the history afterwards does not make the key safe again, because it may already have been copied.

Should the key be encrypted instead of hashed?

Only if the server itself has to use the key later, for example to call another service with it. A key that only has to be checked should be hashed: then there is nothing to decrypt if the database leaks.

Previous in the series ← JSON Web Tokens explained: claims, signatures and safe validation

References

  1. GitHub — Behind GitHub’s new authentication token formats (April 2021)
  2. GitHub Docs — About secret scanning
  3. GitHub Docs — About secret scanning for partners
  4. Stripe — API keys
  5. Stripe — Best practices for managing secret API keys
  6. Google Cloud — Best practices for managing API keys
  7. RFC 6750 — The OAuth 2.0 Authorization Framework: Bearer Token Usage (October 2012)
  8. RFC 7518 — JSON Web Algorithms (JWA) (May 2015)
  9. RFC 4648 — The Base16, Base32, and Base64 Data Encodings (October 2006)
  10. OWASP — Secrets Management Cheat Sheet
  11. OWASP — Password Storage Cheat Sheet
  12. MDN — Crypto: getRandomValues()
  13. MDN — Math.random()

Related guides