An identity platform is mostly a machine for signing things. It signs access tokens, SAML assertions, SSH certificates and service certificates, and it stores the credentials it needs to reach other systems. If each of those private keys is kept wherever the code that uses it happens to run, a single leaked database backup or a single over-broad read permission becomes the ability to sign in as anyone.
The problem with keys kept next to the data
The simplest design stores each private key in the service that uses it: a PEM file on disk, a column in the application database, an environment variable. It works on day one and fails in three predictable ways.
- A database dump is a key dump. Backups, read replicas, support exports and analytics copies all contain the keys.
- Nobody can list the keys. When each service holds its own, there is no single answer to "which keys exist, what does each sign and when was it last rotated".
- Rotation becomes a deployment. If rotating a key means editing configuration in several services, it happens rarely and under pressure.
The fix is not a cleverer place to hide each key. It is one service that holds all of them, signs on request and never hands a private key out.
Envelope encryption in plain terms
Almost every key service is built on envelope encryption. There are two layers of key.
- A data encryption key (DEK) encrypts one thing: a private signing key, a stored secret, a sensitive column.
- A key encryption key (KEK), also called the master key, encrypts the DEKs. It does nothing else.
The encrypted DEK is stored beside the data it protects. The master key is stored somewhere else entirely, behind a separate access boundary: a cloud key management service, a hardware security module, or at the least a different system with different credentials from the database.
stored row = {
ciphertext: AES-GCM(private_key, DEK),
wrapped_dek: wrap(DEK, master),
key_id: "master-2026-a" # which master wrapped this DEK
}
to use it:
DEK = unwrap(wrapped_dek, master[key_id])
private_key = decrypt(ciphertext, DEK)Two details in that sketch carry most of the weight.
The first is that the design is only as strong as the separation between the two stores. If the master key sits in the same database, or in a secret that the same people can read, the envelope adds ceremony and no protection.
The second is the key_id. Every wrapped blob records which master wrapped it. That one field is what makes it possible to change masters later without a flag day, because the service can keep several masters for reading and choose the right one per blob.
What a database dump leaks
Suppose an attacker obtains a full copy of the database.
With keys stored in the clear, they have every signing key. They can mint valid tokens offline, for any user, for as long as verifiers trust those keys.
With envelope encryption and a master held elsewhere, the dump contains ciphertext, wrapped DEKs and metadata. The attacker learns how many keys exist, what each is for and when each was rotated. They cannot sign anything. To do damage they need a second, different compromise: the master key itself, or the ability to call the key service as a trusted caller.
That last point is worth stating honestly. Centralising keys does not remove risk, it concentrates it. The key service becomes the most sensitive component you run, so it needs the tightest access control, the fewest callers and its own entry in the audit record for every operation.
One key per purpose
"One place" does not mean one key. A well-run key service holds many keys, each scoped to a tenant and to a single purpose.
- A key that signs access tokens for people and applications.
- A separate key for machine or agent tokens, so they can be told apart and rotated independently.
- A SAML signing certificate for applications that sign in over SAML 2.0.
- An SSH certificate authority for server access.
- An X.509 certificate authority for service-to-service trust.
- Data keys that seal stored credentials.
Purpose matters because the safe operations differ. A token signing key can be rotated routinely. A certificate authority cannot, because every certificate it has issued chains to it. A key service that knows a key's purpose can offer only the operations that are safe for it.
Rotation with a grace window
Rotating a signing key naively signs everybody out: tokens signed by the old key stop verifying the moment it disappears. The standard remedy is overlap.
- Generate a new key and start signing with it immediately.
- Keep publishing the old public key alongside the new one, at the JWKS address that verifiers already fetch.
- After a window longer than the longest token lifetime, stop publishing the old key.
Verifiers pick the right public key by the kid in each token header, so tokens issued before the rotation keep working until they expire. Nothing is redeployed and nobody notices.
Disabling a key is a different act. It removes the key from publication at once and is the emergency stop for a suspected compromise. The cost is that every token it signed stops verifying. An interface should make it hard to confuse the two.
How AuthFI does it
In AuthFI, signing happens inside the key service of the region a project runs in. No application is handed a private key, and the console shows what each key is for and never the key itself.
- Keys per project and per purpose. Each project has its own token signing key, a separate key for agent tokens, a SAML signer, an SSH authority and a certificate authority for service trust. One project's keys sign nothing for another.
- Envelope encryption with a recorded master id. Signing keys are stored wrapped, and each sealed blob persists the id of the master that wrapped it. The key service registers every configured master as a reader and exactly one as the writer, so a change of master is incremental and reversible: new material lands under the new master while old material keeps opening. A blob whose key id is not recognised is refused, never guessed.
- Rotation without sign-out. A new key signs from the moment you rotate, and the old one stays published for 24 hours so tokens already issued keep verifying. Disabling a key is presented as the emergency stop. An authority key gets no rotate action, so it cannot be rotated by accident.
- Sealed secrets. Credentials for gateways and connections are sealed on arrival and bound to the project that owns them. The console lists names, dates and who added each one. Nothing on the page can reveal a value.
Bring your own key and hardware security module support are in development, not available today. The current list is on the keys and secrets page.
Related reading: short-lived SSH certificates and keeping identity data in your region.
Key takeaways
- Keep every private key behind one service that signs on request and never exports key material.
- Envelope encryption only protects you if the master key lives behind a different boundary from the data.
- Record which master wrapped each blob. It is what makes changing masters gradual and reversible.
- Rotate signing keys with an overlap longer than your longest token lifetime, and keep disable as a separate emergency action.
- Give each key one purpose, so the service can refuse operations that are unsafe for it.



