Cryptography is an architecture decision, not a library choice
Choosing a well-regarded library settles very little. The decisions that determine whether cryptography protects anything are about where keys live, what a compromise reaches, and which threat you are actually addressing.
The question a library cannot answer
A design review that begins “we use AES-256-GCM via a well-maintained library” has answered a question nobody was asking. The primitive is almost certainly fine. Modern libraries make it difficult to select a weak algorithm or misuse a mode, and that class of defect has genuinely declined.
What a library cannot tell you is whether the arrangement achieves anything. A database encrypted with a key that the application server holds in memory is protected against someone who steals a disk and not against anyone who compromises the application—which is the threat that actually occurs. A field encrypted with a key stored in the same table is protected against nothing at all. Both configurations pass a library review and satisfy a compliance question about encryption at rest.
Cryptographic protection is a property of where keys are relative to data and to the components that might be compromised. That is an architecture question, and it is answered on a whiteboard rather than in a dependency manifest.
Four questions before any encryption decision
Before choosing anything, settle four things in writing:
- What compromise is this supposed to survive? A stolen backup, a compromised application server, an over-privileged database role, a curious operator, a subpoena to the hosting provider, a hostile co-tenant. Each implies a different key location.
- Who or what must be able to read the data, and when? If the answer is “the service, on every request”, most key-isolation options are unavailable to you and the realistic protection is narrower than it looks.
- Where can the key be, given that answer? Work outward from the reader: in the reading process, in a local module, in a network service, in a hardware module, in a client the server never sees.
- What happens when the key must change? Rotation, compromise and algorithm deprecation all require re-encryption or key wrapping, and the design either supports that or it does not.
If question one has no specific answer, stop. “Encryption at rest” as a standing requirement, applied without a named threat, reliably produces configurations that satisfy an auditor and protect nothing—and it consumes the budget that would have addressed a real exposure.
Key location is the whole design
The single decision that determines the protection you get is how far the key sits from the component you are worried about. Ordered from weakest to strongest for the common case of a compromised application:
| Key location | Survives | Cost |
|---|---|---|
| Alongside the data | Nothing meaningful | None |
| In application configuration | Stolen storage media | Low |
| In a secrets service, fetched at startup | Stolen media, stolen database | Low |
| In a key service, per-operation call | Compromised application at rest; leaves an audit trail | Latency, availability dependency |
| In a hardware module, key non-exportable | Key extraction; limits an attacker to online use | Cost, operational complexity |
| Held only by the client | Server compromise entirely | No server-side processing; recovery is the user’s problem |
The step from row three to row four is where most of the real protection appears, and it is also the step teams skip, because fetching a key at startup is easy and calling a key service per operation changes the availability model. That trade-off is the conversation worth having, and it is not about cryptography.
NIST SP 800-57 organises the same territory as key states and protection requirements through a key’s life; FIPS 140-3 is the reference for what a validated module guarantees when you choose the hardware-backed option. Neither replaces the architectural decision about which row you are on.
Choose the construction from the property
Once key location is settled, the construction follows from the property you need, and the options are narrow:
- Confidentiality and integrity of a payload with a shared key: an authenticated encryption mode. Never a bare cipher with a separately applied MAC unless you have a specific reason and a reviewer.
- Confidentiality to a recipient’s public key, without a session: HPKE, which packages key encapsulation, derivation and authenticated encryption so the composition is not yours to get wrong.
- Integrity and origin, verifiable by many parties: a signature. A MAC proves origin only to someone who already holds the key, which makes it useless for anything a third party must verify.
- Proof of possession over time: a signature with a nonce or timestamp, bound to the specific request.
- Password verification: a memory-hard function with recorded parameters, never a general-purpose hash.
- Deriving multiple keys from one secret: a labelled key derivation function, with a distinct label per use.
That last item is worth emphasising because it is where otherwise careful designs fail. One key used for encryption, message authentication and token signing creates interactions between uses that no individual primitive protects against. Derive a separate key per purpose from a single root, with the purpose in the derivation label.
Randomness is infrastructure
Almost every construction above depends on a supply of unpredictable values, and almost no design document says where they come from.
The requirements are unglamorous: draw from the operating system’s cryptographic source rather than a general-purpose generator; never seed from a timestamp, process identifier or user-supplied value; and treat a nonce with care appropriate to its mode, because reuse in a counter-based authenticated mode is catastrophic rather than degrading.
Two environments deserve specific attention. Embedded devices may boot with insufficient entropy and generate keys during first-boot provisioning—a known source of duplicated device keys across a product line. Virtualised and container environments may clone a running image, including generator state. In both cases the correct fix is architectural: provision entropy from hardware, or derive per-instance keys after the instance has a unique identity. NIST SP 800-90A covers the generator constructions; the hard part is the seeding path in your deployment.
Build in the ability to change your mind
Every cryptographic decision made today will be revisited, because guidance changes, keys are compromised, and algorithms are deprecated. NIST SP 800-131A is the standing record of that process for key lengths and algorithms, and reading it as history rather than as a current checklist is instructive: things that were acceptable became legacy-use-only and then disallowed, on timescales shorter than many products’ lifespans.
The architectural provisions that make change affordable are small and must be present from the start. Store an algorithm and key identifier alongside every ciphertext, so a reader can dispatch rather than assume. Wrap data keys with a key-encryption key, so rotating the outer key does not require re-encrypting the data. Keep the cryptographic calls behind an internal interface, so the implementation can change in one place. And record, somewhere a human reads, which system uses which construction for what.
None of this is expensive when the system is being designed. All of it is expensive to retrofit across a service estate.
Where designs usually go wrong
The recurring architectural errors are a short list, and none of them is about algorithm choice:
- encryption whose key is reachable by the component the encryption is supposed to defend against;
- one key serving several purposes, with no derivation;
- a ciphertext with no algorithm or key identifier, making migration a rewrite;
- data keys with no wrapping layer, making rotation a re-encryption project;
- integrity assumed from a confidentiality mode that does not provide it;
- a key with no defined lifetime, so it has been in use since the system was built;
- a recovery path—an escrow copy, a break-glass export, a support tool—that is the weakest point in the design and was never reviewed.
The last one is the most common finding in an architecture assessment. The primary path is often well designed by people who thought carefully about it. The recovery path was added later, by someone solving an operational problem, and it holds a copy of the key in a less protected place. An assessment that only reviews the intended flow will miss it entirely.