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