Lab: build envelope encryption by hand
Do the KEK and DEK dance yourself with openssl, then destroy a key and watch the data become unreadable.
The previous lesson described envelope encryption. Ten minutes with openssl makes it concrete, and nothing here needs an account, a network, or 1Claw. Everything happens in a scratch directory you delete afterwards.
You need openssl, which ships with macOS and every Linux distribution. Run openssl version to confirm.
- 1
Make a scratch directory so nothing lands in a real project.
bashmkdir -p /tmp/envlab && cd /tmp/envlab - 2
Generate the key encryption key. In production this lives inside an HSM and never comes out; here it is a file, which is exactly the weakness the HSM removes.
bashopenssl rand -hex 32 > kek.hex - 3
Generate a data encryption key for one secret. Every secret gets its own, which is what limits the blast radius of a single compromise.
bashopenssl rand -hex 32 > dek.hex - 4
Encrypt the secret under the DEK. Save the IV: it is not sensitive, but without it the ciphertext is undecryptable, which is a very common way to lose data.
bashprintf 'sk-live-abc123' > secret.txt openssl rand -hex 16 > iv.hex openssl enc -aes-256-cbc \ -K "$(cat dek.hex)" -iv "$(cat iv.hex)" \ -in secret.txt -out secret.enc - 5
Wrap the DEK under the KEK. This is the envelope: the file you store next to the ciphertext is the encrypted key, never the key itself.
bashopenssl rand -hex 16 > wiv.hex openssl enc -aes-256-cbc \ -K "$(cat kek.hex)" -iv "$(cat wiv.hex)" \ -in dek.hex -out dek.wrapped - 6
Look at what a server would actually hold. There is no plaintext key anywhere in this list.
bashls -l secret.enc dek.wrapped iv.hex wiv.hex - 7
Read the secret back the way the vault does: unwrap the DEK with the KEK, then decrypt with the DEK.
bashopenssl enc -d -aes-256-cbc \ -K "$(cat kek.hex)" -iv "$(cat wiv.hex)" \ -in dek.wrapped -out dek.recovered openssl enc -d -aes-256-cbc \ -K "$(cat dek.recovered)" -iv "$(cat iv.hex)" \ -in secret.enc # -> sk-live-abc123 - 8
Before erasing anything, rotate the KEK. Take a fingerprint of the ciphertext, unwrap the DEK with the old KEK, generate a new KEK, and re-wrap the same DEK under it.
bashshasum -a 256 secret.enc openssl enc -d -aes-256-cbc \ -K "$(cat kek.hex)" -iv "$(cat wiv.hex)" \ -in dek.wrapped -out dek.plain openssl rand -hex 32 > kek2.hex openssl enc -aes-256-cbc \ -K "$(cat kek2.hex)" -iv "$(cat wiv.hex)" \ -in dek.plain -out dek.wrapped2 - 9
Now fingerprint the ciphertext again, and compare the sizes of what rotation actually rewrote.
bashshasum -a 256 secret.enc wc -c secret.enc dek.wrapped2 - 10
Identical hash. You rotated the root of the hierarchy and the encrypted data was never opened, read or rewritten: rotation touched 80 bytes of wrapped key and left the ciphertext alone. On a real vault that is the difference between re-encrypting a small key and re-encrypting a database.
text<hash> secret.enc (before) <hash> secret.enc (after, identical) 16 secret.enc 80 dek.wrapped2 96 total - 11
Now do cryptographic erasure. Delete only the wrapped DEK and try again.
bashrm dek.wrapped dek.recovered openssl enc -d -aes-256-cbc \ -K "$(cat kek.hex)" -iv "$(cat wiv.hex)" \ -in dek.wrapped -out dek.recovered # -> Can't open "dek.wrapped" for reading, No such file or directory - 12
The ciphertext is untouched and permanently unreadable. Clean up.
bashcd /tmp && rm -rf envlab
Three properties you just demonstrated rather than read about. The KEK never touched the secret, only the DEK. Rotating the KEK would mean re-encrypting one small wrapped key, not the data. And deleting a key erased the data instantly, without overwriting a single byte of it.
Real implementations use AES-GCM rather than CBC, because GCM authenticates as well as encrypts, so tampering is detected instead of producing garbage plaintext. CBC is used here only because it needs fewer flags to demonstrate.
You just held a KEK in a file, which is the thing the next lesson exists to fix. Everything above is correct and still weak, because the root of the hierarchy sat on disk where any process could read it.
Check your understanding
3 questionsYou deleted the wrapped DEK but kept the ciphertext. Why is the data unrecoverable?
Why does each secret get its own DEK rather than sharing one?
The IV was stored in plaintext next to the ciphertext. Is that a problem?