Envelope encryption explained: Why you probably shouldn't implement it yourself
The two-layer key pattern behind AWS KMS, Google Cloud KMS, and WorkOS Vault, and why rolling your own is riskier than it looks.
Most encryption guides stop at "use AES-256." That advice is correct and also almost useless, because the hard part of encryption at scale is not the algorithm. It is managing the keys.
Use a single key for everything and you have a single point of failure. Rotate it and you need to re-encrypt every byte of data you have ever stored. Lose it and your data is gone. Hand it to your application and it sits in an environment variable, waiting to be leaked in a log.
Envelope encryption is the pattern that production systems use to escape this problem. It is how AWS KMS, Google Cloud KMS, Azure Key Vault, and WorkOS Vault all work under the hood. Understanding it properly will change how you think about key management and save you from building something you don't need to build.
The problem with one key
Imagine you are building a SaaS application that stores sensitive user data: email addresses, phone numbers, API credentials. You decide to encrypt it. You generate an AES-256 key, store it in an environment variable, and encrypt every field before it hits the database.
This is better than nothing. But now ask yourself: what happens when you need to rotate the key?
To rotate, you need to decrypt every encrypted field with the old key and re-encrypt it with the new one. For a small dataset, that is a weekend job. For a database with millions of rows, it is a migration that takes hours, requires coordination across services, and has a real chance of going wrong. So most teams skip it. The key sits there for years, accumulating risk.
Now ask: what happens when a customer asks you to use their own encryption key? Or when a security audit reveals the key has been exposed in an old log file?
The one-key model does not scale. Envelope encryption is the solution.
How envelope encryption works
The core idea is splitting encryption into two layers, each with a different job.
- The Data Encryption Key (DEK) is a random key generated fresh for each encryption operation. It encrypts the actual data. DEKs are short-lived, unique per object, and never stored in plaintext anywhere.
- The Key Encryption Key (KEK) is a long-lived key tied to a tenant, a user, or a data category. Its only job is to encrypt DEKs. It never touches the data directly. The KEK lives in a Hardware Security Module (HSM), a physical device that stores cryptographic keys and cannot export them to plaintext. The key can only be used to perform operations; it cannot be read out.
When you encrypt a piece of data, the process looks like this:
- Generate a fresh random DEK.
- Encrypt your data with the DEK using AES-256-GCM.
- Send the DEK to the HSM, which wraps it with the KEK.
- Store two things together: the encrypted data and the wrapped (encrypted) DEK.
- Discard the plaintext DEK from memory.
When you need to read the data back:
- Retrieve the encrypted data and the wrapped DEK.
- Send the wrapped DEK to the HSM, which unwraps it with the KEK.
- Use the plaintext DEK to decrypt the data.
- Discard the plaintext DEK from memory.
The KEK never leaves the HSM. The plaintext DEK exists only in memory for the duration of the operation and is never written to disk. What gets stored is always ciphertext: encrypted data wrapped around an encrypted key.
Why this design solves the problems that one-key doesn't
Key rotation becomes cheap
With a single key, rotating means re-encrypting all your data. With envelope encryption, the KEK wraps DEKs, not data. To rotate a KEK, you re-encrypt the DEKs: small fixed-size key material, not your entire dataset. The data itself does not move. A rotation that would have taken hours now takes seconds.
Blast radius is bounded per object
Every encrypted object has a unique DEK. If an attacker somehow obtains a single DEK (through a memory dump, a log leak, or a bug) they can decrypt one object. Not all objects encrypted under the same KEK. The damage is contained to the unit of the DEK.
The KEK is a second, separate layer of protection. Without access to the HSM to unwrap the DEK, the ciphertext is useless even if the attacker has both the encrypted data and the encrypted DEK.
Multi-tenancy becomes tractable
Give each tenant their own KEK and you have cryptographic isolation between tenants. One organization's data is encrypted with their DEKs, which are wrapped by their KEK. Another organization's KEK cannot unwrap them. A bug that leaks ciphertext from one tenant to another session is a data leak; it is not a data breach, because the receiving session cannot decrypt what it received.
This is the architecture that makes per-tenant BYOK possible. If a customer wants to bring their own encryption key and use it as their KEK, the application code does not change. The HSM integration simply uses their key instead of a managed one. When they rotate or revoke their key, it takes effect immediately across all their data.
Key management stays centralized without becoming a bottleneck
The HSM is the source of truth for all KEKs. You do not need to distribute, store, or track key material across your infrastructure. Every decryption operation goes through the HSM, which also means every operation is auditable. You have a complete log of what was decrypted, by whom, and when. That level of auditability is nearly impossible to achieve with application-managed keys.
What actually gets stored
This is the part that confuses people the most, so it is worth being explicit.
After an encryption operation, your storage contains:
None of this is sensitive in isolation. The encrypted data cannot be read without the DEK. The wrapped DEK cannot be read without the KEK. The KEK cannot be exported from the HSM. An attacker who exfiltrates your entire database has a pile of ciphertext they cannot use.
The key context is metadata that tells the key management system which KEK to use when unwrapping. In WorkOS Vault, this is the context parameter you pass when creating an object: { organizationId: "org_acmecorp" }. Vault resolves the correct KEK from the context at decryption time. You do not manage key IDs.
Where most implementations go wrong
Understanding the theory is one thing. The failure modes are in the details.
- Storing plaintext DEKs. The DEK should exist in memory only for the duration of an operation. If it gets logged, written to a file, or stored in a database column, the second layer of protection disappears. The encrypted DEK is what you store. Never the plaintext one.
- Reusing DEKs across objects. A DEK should be generated fresh per encryption operation, not per session, per day, or per user. Reusing a DEK across multiple objects means compromising one DEK compromises all of them. Fresh-per-operation DEKs are the whole point of the bounded blast radius.
- Not using an HSM. Storing the KEK in an environment variable or a config file defeats the purpose. The KEK needs to live somewhere the application cannot read it directly; it can only submit operations to it. That is what HSMs are for. In practice this means using a managed service (AWS KMS, Google Cloud KMS, Azure Key Vault) or a purpose-built system like WorkOS Vault, not implementing your own key store.
- Skipping the key context. If you use the same KEK for all tenants, you have better key management but no cryptographic isolation. The key context (the metadata that maps an operation to a specific KEK) is what creates per-tenant boundaries. Without it, a single KEK compromise still exposes every tenant's data.
- Not planning for rotation. The operational advantage of envelope encryption is that rotation is cheap. But cheap does not mean automatic. You need a rotation schedule, a process for re-wrapping DEKs under a new KEK version, and confidence that old KEK versions remain available for unwrapping existing data until the migration is complete.
The case for not implementing this yourself
Envelope encryption is a well-understood pattern. The algorithms are standardized (AES-256-GCM for data encryption, AES Key Wrap per RFC 3394 for DEK wrapping). The theory is not complicated.
The implementation is a different story.
Building a production envelope encryption system means: provisioning and managing an HSM or cloud KMS integration, implementing DEK generation with a cryptographically secure random source, ensuring plaintext DEKs are zeroed from memory after use, building key context storage and resolution, implementing KEK rotation without service downtime, handling the edge cases around concurrent writes and version conflicts, building audit logging for every key operation, and testing all of it correctly. A bug in cryptographic code is usually silent and catastrophic rather than noisy and recoverable.
Most teams that build this themselves get the happy path right and the edge cases wrong. The DEKs are generated correctly but logged somewhere. The rotation works in testing but has a race condition under load. The HSM integration handles the common case but not the failure modes.
WorkOS Vault implements envelope encryption as a managed service. You call createObject with a value and a key context. Vault generates the DEK, encrypts the value, wraps the DEK using an HSM-backed KEK derived from your context, stores the ciphertext pair, and returns an object ID. You call readObject with that ID and get the plaintext back. The DEK lifecycle, the HSM operations, the key context resolution, and the rotation infrastructure are handled for you.
The key context { organizationId: 'org_acmecorp' } is the mechanism that creates cryptographic isolation between tenants. Each unique context maps to a separate KEK. The multi-tenant isolation, the BYOK support, and the rotation infrastructure all follow from this single decision at the API level.
When you actually need to understand the internals
Most developers using Vault or AWS KMS never need to think about DEKs and KEKs explicitly. The managed service abstracts them.
There are three situations where the underlying model matters:
- Threat modeling. When a security engineer asks "what is the blast radius if our database is compromised?" or "what would an attacker need to decrypt a specific object?", you need to understand the DEK and KEK layers to answer correctly. The answer is: they need both the encrypted DEK and access to the HSM to unwrap it. Neither alone is sufficient.
- Compliance conversations. HIPAA, PCI DSS, and SOC 2 all have language about encryption key management. Auditors ask about key rotation, key storage, and access controls. Understanding envelope encryption lets you answer these questions precisely rather than vaguely.
- BYOK conversations. When an enterprise customer asks to bring their own key, what they are asking for is to supply their own KEK. Understanding the model tells you that this is a supported swap at the KEK layer, it does not require re-encrypting any data, and it does not change the application code. Only the HSM operation that wraps and unwraps the DEK is different.
The short version
Envelope encryption uses two layers of keys with two different jobs. The DEK encrypts data and is unique per object. The KEK encrypts DEKs and lives in an HSM where it cannot be exported. What gets stored is always ciphertext: encrypted data and an encrypted DEK, never either key in plaintext.
This design makes key rotation cheap (re-wrap DEKs, not data), bounds the blast radius of a compromise (per-DEK, not per-KEK), enables multi-tenant cryptographic isolation (one KEK per tenant), and supports BYOK (swap the KEK, nothing else changes).
The pattern is well understood. The implementation is subtle and the failure modes are silent. Unless key management is core to your product, the right move is to use a system that has already solved it.
Further reading:
- Cryptographic key isolation in multi-tenant SaaS: how key context creates tenant boundaries in Vault
- Encrypting PII in a Node.js app with WorkOS Vault: a practical tutorial using the full CRUD lifecycle
- Vault quick start: get started in minutes
- BYOK documentation: letting enterprise customers supply their own KEK