Understanding AWS KMS Envelope Encryption
How AWS Key Management Service uses a two-tier key hierarchy to encrypt large data without exposing master keys outside HSM boundaries.
Introduction
AWS Key Management Service (KMS) stores encryption keys in FIPS 140-2 validated Hardware Security Modules (HSMs) — tamper-resistant physical devices where keys are generated and never leave in plaintext. However, sending gigabytes of data to KMS for encryption would be impossibly slow and expensive. Envelope encryption solves this: KMS encrypts a small Data Encryption Key (DEK), and the DEK encrypts the actual data locally. The plaintext DEK exists only in memory during the operation.
flowchart TD
subgraph W["Write path"]
E1["App calls KMS GenerateDataKey"] --> E2["KMS returns plaintext DEK + encrypted DEK"]
E2 --> E3["App encrypts data locally with the plaintext DEK"]
E3 --> E4["Plaintext DEK discarded; ciphertext + encrypted DEK stored"]
end
subgraph R["Read path"]
D1["App sends encrypted DEK to KMS Decrypt"] --> D2["KMS returns plaintext DEK"]
D2 --> D3["App decrypts data, then discards plaintext DEK"]
end
How It Works in Practice
Key Hierarchy
AWS KMS Customer Master Key (CMK / KMS Key)
├─ Lives in HSM, never exported in plaintext
├─ Used ONLY to encrypt/decrypt Data Encryption Keys
└─ Has key policy controlling who can use it
Data Encryption Key (DEK)
├─ AES-256 symmetric key generated per object/session
├─ Plaintext DEK: used temporarily in memory to encrypt data
├─ Encrypted DEK: stored alongside ciphertext (safe to store anywhere)
└─ Plaintext DEK is immediately zeroed after use
Generate a Data Key
aws kms generate-data-key --key-id alias/prod-encryption-key --key-spec AES_256
# Response:
{
"KeyId": "arn:aws:kms:us-east-1:123456789:key/abcd-1234",
"Plaintext": "base64-encoded-32-byte-AES-key", # Use this, then discard
"CiphertextBlob": "base64-encoded-encrypted-key" # Store this with your data
}
Encrypt and Decrypt Flow
import boto3, base64
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
import os
kms = boto3.client('kms')
def encrypt_data(plaintext: bytes, kms_key_id: str) -> dict:
# 1. Generate DEK
response = kms.generate_data_key(KeyId=kms_key_id, KeySpec='AES_256')
plaintext_dek = response['Plaintext'] # Use for encryption
encrypted_dek = response['CiphertextBlob'] # Store with ciphertext
# 2. Encrypt data with DEK (local, no KMS call)
nonce = os.urandom(12) # 96-bit nonce for AES-GCM
cipher = AESGCM(plaintext_dek)
ciphertext = cipher.encrypt(nonce, plaintext, None)
# 3. Zero the plaintext DEK (security hygiene)
plaintext_dek = b'�' * len(plaintext_dek)
return {
'encrypted_dek': base64.b64encode(encrypted_dek).decode(),
'nonce': base64.b64encode(nonce).decode(),
'ciphertext': base64.b64encode(ciphertext).decode()
}
def decrypt_data(envelope: dict, kms_key_id: str) -> bytes:
# 1. Decrypt DEK using KMS (requires kms:Decrypt permission)
encrypted_dek = base64.b64decode(envelope['encrypted_dek'])
response = kms.decrypt(CiphertextBlob=encrypted_dek, KeyId=kms_key_id)
plaintext_dek = response['Plaintext']
# 2. Decrypt data locally
nonce = base64.b64decode(envelope['nonce'])
ciphertext = base64.b64decode(envelope['ciphertext'])
cipher = AESGCM(plaintext_dek)
return cipher.decrypt(nonce, ciphertext, None)
KMS Key Policy
{
"Statement": [
{
"Sid": "Allow data encryption by app role",
"Effect": "Allow",
"Principal": {"AWS": "arn:aws:iam::123456789:role/AppRole"},
"Action": ["kms:GenerateDataKey", "kms:Decrypt"],
"Resource": "*",
"Condition": {
"StringEquals": {
"kms:ViaService": "s3.us-east-1.amazonaws.com"
}
}
}
]
}
Key Takeaways
- KMS keys never leave the HSM in plaintext — all encryption/decryption happens either inside the HSM or locally with DEKs that KMS generates.
- Envelope encryption decouples key management (KMS) from data encryption (local AES-GCM) — enabling high throughput without KMS API rate limits.
- Store only the encrypted DEK and ciphertext — without the KMS CMK, the encrypted DEK is useless to an attacker.
- Use KMS key conditions (ViaService, SourceVpc) in key policies to restrict where keys can be used.
- Enable automatic key rotation (annually for AWS-managed keys, configurable for customer-managed) — KMS transparently re-encrypts existing DEKs on rotation.
Historical figures, architectures, and capabilities are for informational purposes only. Not technical, professional, legal, or financial advice. Sources: Research papers, developer documentation.