Envelope Encryption Pattern
Overview
Envelope encryption is a pattern where a data key encrypts the data, and a KMS key encrypts the data key. This approach combines the security of KMS with the efficiency of symmetric encryption for large datasets.
Prerequisites
- AWS account with appropriate permissions
- Terraform >= 1.0
- AWS CLI configured
Step 1: Create the Master Key
Create a KMS customer managed key to encrypt data keys:
module "master_key" {
source = "registry.patterneddesigns.ca/patterneddesigns/kms-key/aws"
version = "1.0.0"
alias = "alias/envelope-master"
description = "Master key for envelope encryption"
}
output "master_key_id" {
value = module.master_key.key_id
}
Step 2: Generate Data Keys
Use the AWS CLI or SDK to generate data keys encrypted by your master key:
# Generate a data key
aws kms generate-data-key \
--key-id alias/envelope-master \
--key-spec AES_256
This returns:
Plaintext: The unencrypted data key (use for encryption, then discard)CiphertextBlob: The encrypted data key (store alongside your encrypted data)
Step 3: Encrypt Data with Data Key
Use the plaintext data key to encrypt your data using a symmetric algorithm:
import boto3
from cryptography.fernet import Fernet
import base64
def encrypt_data(data, key_alias):
kms = boto3.client('kms')
# Generate a data key
response = kms.generate_data_key(
KeyId=key_alias,
KeySpec='AES_256'
)
plaintext_key = response['Plaintext']
encrypted_key = response['CiphertextBlob']
# Encrypt the data with the plaintext key
fernet = Fernet(base64.urlsafe_b64encode(plaintext_key))
encrypted_data = fernet.encrypt(data.encode())
# Return encrypted data and encrypted key
# (plaintext key should be discarded)
return {
'encrypted_data': encrypted_data,
'encrypted_key': encrypted_key
}
Step 4: Decrypt Data
To decrypt, first decrypt the data key with KMS, then use it to decrypt the data:
def decrypt_data(encrypted_data, encrypted_key):
kms = boto3.client('kms')
# Decrypt the data key
response = kms.decrypt(
CiphertextBlob=encrypted_key
)
plaintext_key = response['Plaintext']
# Decrypt the data with the plaintext key
fernet = Fernet(base64.urlsafe_b64encode(plaintext_key))
decrypted_data = fernet.decrypt(encrypted_data)
return decrypted_data.decode()
Step 5: Implement in Terraform
For services that support envelope encryption natively:
# S3 with bucket keys (envelope encryption)
resource "aws_s3_bucket_server_side_encryption_configuration" "envelope" {
bucket = aws_s3_bucket.data.id
rule {
apply_server_side_encryption_by_default {
kms_master_key_id = module.master_key.key_arn
sse_algorithm = "aws:kms"
}
bucket_key_enabled = true # Enables envelope encryption
}
}
Benefits
- Performance: Symmetric encryption is faster than KMS for large data
- Cost Reduction: Fewer KMS API calls with bucket keys
- Security: Data keys are protected by KMS without exposing master key
- Scalability: Suitable for high-volume encryption workloads