How Envelope Encryption Secures Object Storage Buckets
A technical walkthrough of KMS envelope encryption — DEKs, CMKs, and how S3 server-side encryption actually works.
Introduction
Encrypting large datasets presents a challenge: if you encrypt every S3 object directly with a KMS key, every read/write requires a KMS API call — expensive, slow, and constrained by KMS quotas (30,000 requests/second per key). Envelope encryption solves this by using a hierarchy of keys: a long-lived Customer Master Key (CMK) in KMS encrypts short-lived Data Encryption Keys (DEKs), which encrypt the actual data locally. KMS never sees the data; it only wraps and unwraps DEKs.
Step-by-Step: How Envelope Encryption Works
flowchart TD
A["1. Key Hierarchy"]
B["2. Encryption Flow (Write Path)"]
C["3. Decryption Flow (Read Path)"]
D["4. S3 Bucket Keys (Cost Optimization)"]
E["5. Key Policy and Access Control"]
A --> B
B --> C
C --> D
D --> E
Step 1: Key Hierarchy
AWS KMS Customer Master Key (CMK)
→ Stored in HSM-backed KMS, never leaves KMS in plaintext
→ Used only to encrypt/decrypt Data Encryption Keys
Data Encryption Key (DEK)
→ AES-256 key generated per object (or per session)
→ Encrypted by CMK (stored alongside the ciphertext data)
→ Plaintext DEK exists only in memory during encryption/decryption
Step 2: Encryption Flow (Write Path)
1. Application calls: aws kms generate-data-key --key-id alias/my-cmk
2. KMS returns:
- Plaintext DEK (use to encrypt data, then discard from memory)
- Encrypted DEK (store alongside ciphertext in S3 metadata)
3. Application encrypts object with plaintext DEK using AES-256-GCM
4. Stores ciphertext object + encrypted DEK to S3
5. Plaintext DEK is zeroed from memory
For S3 SSE-KMS, AWS automates steps 1-5 transparently:
# Enable SSE-KMS on bucket
aws s3api put-bucket-encryption --bucket my-prod-bucket --server-side-encryption-configuration '{
"Rules": [{
"ApplyServerSideEncryptionByDefault": {
"SSEAlgorithm": "aws:kms",
"KMSMasterKeyID": "arn:aws:kms:us-east-1:123456789:key/abcd-1234"
},
"BucketKeyEnabled": true
}]
}'
Step 3: Decryption Flow (Read Path)
1. Retrieve ciphertext object + encrypted DEK from S3
2. Call: aws kms decrypt --ciphertext-blob <encrypted-DEK>
3. KMS verifies caller has kms:Decrypt permission on CMK
4. KMS returns plaintext DEK
5. Decrypt ciphertext object with plaintext DEK
6. Zero plaintext DEK from memory
Step 4: S3 Bucket Keys (Cost Optimization)
Without Bucket Keys, every S3 object operation generates a KMS API call. With S3 Bucket Keys, a per-bucket DEK is cached at the S3 level for up to 7 days — reducing KMS calls by up to 99%:
Without Bucket Keys: 1,000,000 object reads = 1,000,000 KMS API calls (~$3/hour)
With Bucket Keys: 1,000,000 object reads = ~1 KMS call per session
Step 5: Key Policy and Access Control
{
"Sid": "Allow S3 to use this key for bucket encryption",
"Effect": "Allow",
"Principal": {"Service": "s3.amazonaws.com"},
"Action": ["kms:GenerateDataKey", "kms:Decrypt"],
"Resource": "*",
"Condition": {
"StringEquals": {
"aws:SourceAccount": "123456789012"
}
}
}
Key Takeaways
- Envelope encryption separates the key-encrypting key (CMK in KMS) from the data-encrypting key (DEK), so KMS never handles bulk data.
- KMS
GenerateDataKeyreturns both a plaintext DEK (use and discard) and an encrypted DEK (store with ciphertext). - S3 Bucket Keys cache the DEK per bucket at the S3 layer, reducing KMS API costs by up to 99% for high-throughput workloads.
- The CMK never leaves KMS in plaintext — decryption requires an explicit
kms:Decryptpermission check on every read. - For compliance workloads, use customer-managed CMKs (not AWS-managed) to retain key rotation control and audit key usage via CloudTrail.
Historical figures, architectures, and capabilities are for informational purposes only. Not technical, professional, legal, or financial advice. Sources: Research papers, developer documentation.