An API key is often treated like a password: a client presents a secret string, and the server decides whether that string represents an authorized caller. Yet many systems store API keys in plaintext because the application needs to compare them later.
That design creates an avoidable consequence. If an attacker obtains the credential database, every stored plaintext key may immediately become a usable credential. The database leak becomes an authentication compromise as well as a data leak.
For randomly generated, high-entropy API keys that the server only needs to verify, a better model is often possible: store a one-way verifier instead of the original key. The server can check a presented key without retaining a recoverable copy of it.
This article explains when that model works, why API-key hashing differs from password hashing, how to structure lookup and verification, and what risks remain after plaintext storage is removed.
Start with what the server actually needs
Suppose a service creates an API key for a client. After creation, the normal authentication flow is simple:
client presents key
|
v
server identifies credential
|
v
server verifies key
|
v
load permissions and continueNotice what is missing: the server does not need to display the original key again, send it to another system, or recover it for later use. It only needs to answer one question:
Does the presented value match the credential we issued?That is a verification problem, not a secret-recovery problem.
If the product promises that users can return later and reveal an existing API key, the server must retain enough information to recover that value. Encryption can protect such stored data under an appropriate key-management design, but whoever can decrypt it can recover the credential. When recovery is unnecessary, storing only a one-way verifier removes that capability from the credential database.
The threat model is a leaked credential store
This control primarily reduces the damage from disclosure of the database or datastore that contains API-key records.
With plaintext storage, a stolen record can contain the credential itself:
credential record -> usable API keyWith one-way verification, the record instead contains a derived value:
credential record -> verifier
presented key -> derive verifier -> compareAn attacker who obtains only the verifier should not be able to directly turn it back into the original randomly generated key. The attacker can still try candidate keys and compare their derived values, so the original keys must have enough unpredictable entropy to make guessing infeasible under the system’s threat model.
This control does not protect a key stolen from a client, source repository, build log, browser, process, proxy, or compromised application server. It also does not repair excessive permissions, missing rate controls, weak key generation, or authorization mistakes. If the live application can authenticate a presented key, compromise of that application may also let an attacker abuse the authentication path.
The narrower guarantee is still valuable: disclosure of the verifier store does not automatically disclose the original high-entropy keys.
High-entropy API keys are not human passwords
Passwords and generated API keys may both be verified with stored derived values, but their guessing risks are different.
Human-chosen passwords often come from a relatively predictable space. Attackers can test common words, reused passwords, and variations efficiently after obtaining password hashes. Password storage therefore uses dedicated password-hashing functions designed to make each guess deliberately expensive, with parameters chosen for the application’s environment.
A properly generated API key can instead contain substantial cryptographic randomness. For example, a key generated from 32 random bytes has 256 bits of raw entropy before encoding, assuming the random generator is cryptographically suitable and all generated values are allowed. Exhaustively guessing that space is not a practical strategy with current computing methods.
Under that assumption, a standard cryptographic hash or a keyed message authentication code can serve as a verifier without the intentionally expensive work factor used for human passwords. The important assumption is high-entropy generation. Hashing a short, user-chosen, sequential, or otherwise predictable API key does not make it resistant to offline guessing.
Do not copy password-storage parameters into an API-key design without understanding this distinction, and do not use fast hashing as an excuse to accept weak API keys.
Separate lookup from secret verification
A service still needs an efficient way to find the correct credential record. Searching every stored verifier for every request is unnecessary and becomes expensive as the credential set grows.
A practical API key can contain two logical parts:
public identifier + secret valueThe public identifier is not an authenticator. It exists only to locate the credential record. The secret value carries the authentication strength.
A simplified record might contain:
credential_id: public identifier
verifier: one-way value derived from secret portion
principal_id: owning service or account
status: active or revoked
created_at: creation timeAuthentication then becomes:
1. Parse the public credential identifier.
2. Load that credential record.
3. Derive a verifier from the presented secret portion.
4. Compare the derived and stored verifiers with an appropriate constant-time comparison function.
5. Check credential status and applicable policy.
6. Continue only if every required check succeeds.This structure keeps database lookup independent from proof of possession. Knowing a credential identifier may reveal that a record exists, depending on the API design, but it must not be enough to authenticate.
The exact external key format is an application decision. Keep parsing strict, version the format if future changes are likely, and avoid embedding sensitive account data into the public identifier.
Choose the verifier deliberately
Two common constructions fit high-entropy generated keys.
The first is a cryptographic hash:
verifier = HASH(secret_value)The second is a keyed construction such as HMAC:
verifier = HMAC(server_pepper, secret_value)A plain cryptographic hash can be sufficient when the API key has enough unpredictable entropy. A database-only attacker receives the verifier but still faces the impractical task of guessing the random secret.
A keyed verifier adds a separate server-held secret, sometimes called a pepper. If the verifier database is stolen without that pepper, the attacker cannot even evaluate candidate verifiers in the same way. This can provide additional separation when the pepper is stored behind a different security boundary, such as a dedicated secrets or key-management system.
That additional control also creates operational work. The pepper must be available to authentication services, protected from disclosure, recoverable during legitimate outages, and rotatable without unexpectedly invalidating every credential. If the database and pepper are routinely exposed together, the extra boundary provides less value.
Use established cryptographic library functions rather than inventing a custom transform. The choice between an unkeyed hash and HMAC should follow the threat model and operational capability, not the idea that more cryptography is automatically better.
Show the key once, then lose the ability to recover it
A verifier-only design changes the credential lifecycle.
At creation time, generate the secret with a cryptographically secure random generator, calculate its verifier, store the credential record, and return the complete API key to the authorized client over the protected channel used by the application.
After that response, the service should not retain the plaintext key merely for convenience. A later management page can show metadata such as the credential name, creation time, last-used time if collected, status, and a non-secret identifying prefix. It should not claim to reveal the original key.
If the client loses the key, recovery means creating a replacement credential, updating the client, and revoking the lost credential when appropriate. This is a usability trade-off: verifier-only storage reduces recoverability on purpose.
That trade-off is usually acceptable for machine credentials because clients should already have a controlled place to store their copy. It may be inappropriate when a system genuinely must reproduce the same secret for another trusted component. In that case, the requirement is secret storage rather than verification-only storage, and encryption plus disciplined key management is the relevant design problem.
Make revocation independent of the verifier
Hashing does not solve credential lifecycle management. A valid key may need to stop working because a client was decommissioned, an employee left, a repository leaked a credential, or routine rotation replaced it.
Keep revocation state in the credential record rather than trying to encode permanent authority into the key itself. Authentication should check that the record still exists and is active after the secret matches.
For systems with cached authentication decisions, define how quickly revocation must take effect. A cache that treats a key as active for ten minutes can create roughly a ten-minute stale-access window unless there is a faster invalidation mechanism. Whether that is acceptable depends on the key’s authority and the application’s incident-response needs.
Also make rotation support overlapping credentials when clients cannot switch atomically. Create the replacement, deploy it, confirm use has moved, then revoke the old credential. Verifier-only storage does not require an outage during rotation.
Do not let observability recreate plaintext storage
Removing plaintext keys from the credential table is useful only if other systems do not collect them again.
Authentication code should avoid placing presented keys in application logs, tracing attributes, error reports, analytics events, or exception messages. Reverse proxies and API gateways should also be configured so that credential-bearing headers are not recorded verbatim.
For audit purposes, record a non-secret credential identifier rather than the secret value. That lets responders answer which credential was used without turning the audit system into a second credential store.
Be careful with debug modes. Logging an entire request object during an authentication failure can quietly undo the verifier-only design by copying the presented key into a log platform with a much wider audience and longer retention period.
Verify the control with failure-oriented tests
A useful test suite should prove more than successful authentication.
Create a credential and inspect the persisted record. The complete key should not be present. Present the correct key and confirm authentication succeeds. Change one part of the secret and confirm it fails. Revoke the record and confirm the previously valid key no longer works according to the documented revocation timing.
Then inspect logs, traces, error reports, and request-capture tooling produced by those tests. Search for the complete test credential. The value should not appear in systems that do not need it.
If the design uses a server-side pepper, test the operational failure modes too. Confirm authentication fails in the intended way when the pepper cannot be retrieved, and confirm recovery procedures restore service without introducing a bypass. A dependency outage must not turn an unverifiable credential into an accepted one.
Finally, test key generation itself. The generator must use the platform’s cryptographically secure random facility, produce the intended amount of randomness, and avoid transformations that accidentally shrink the secret space.
Know when this control is enough
Verifier-only storage is a strong fit when API keys are generated randomly, clients present them for authentication, and the server never needs the original value after issuance.
For low-authority credentials with short lifetimes and strong random generation, a straightforward hash-based verifier may be sufficient. Higher-impact credentials may justify additional controls: a separately protected HMAC key, shorter lifetimes, narrower permissions, stronger monitoring, faster revocation, or moving away from long-lived bearer credentials where the architecture supports a better authentication mechanism.
The verifier does not change the fact that a conventional API key is a bearer credential: whoever obtains the plaintext key can generally use it within its permissions until it expires or is revoked. Limit those permissions, give credentials intentional lifetimes, rotate them when exposure is suspected, and keep the plaintext value out of unnecessary systems.
Conclusion
If a server only needs to verify a generated API key, it usually does not need to keep a recoverable copy of that key.
Store a public lookup identifier and a one-way verifier, generate the secret with sufficient cryptographic randomness, compare verifiers with an appropriate constant-time function, and keep revocation state separate from the secret. Consider a keyed verifier when a separately protected pepper meaningfully improves the threat model and the operational cost is justified.
The practical goal is not to make API keys harmless. It is to ensure that one common failure—a leaked credential database—does not automatically become a collection of ready-to-use plaintext credentials.