Skip to content
intermediate Phase 6 · AWS Security & Compliance

KMS & Encryption

Encrypt data at rest and in transit with AWS KMS and encryption helpers.

1h
0 problems
Topic Progress 0%

KMS Fundamentals

KMS Fundamentals

AWS Key Management Service (KMS) creates and manages cryptographic keys for encrypting data.

Key Concepts

Concept Description
CMK Customer Master Key - root of trust
Data Key Encrypts/decrypts actual data
Envelope Encryption CMK encrypts data keys
Key Policy IAM policy for key access
Grants Alternative to key policies

Create CMK

# Create a symmetric CMK
aws kms create-key \
  --description "My application encryption key" \
  --tags TagKey=Purpose,TagValue=AppEncryption

# Create alias
aws kms create-alias \
  --alias-name alias/my-app-key \
  --target-key-id key-id

# Enable key rotation
aws kms enable-key-rotation --key-id key-id

# Describe key
aws kms describe-key --key-id alias/my-app-key

# List keys
aws kms list-keys

Key States

State Description
Enabled Key can be used
Disabled Key cannot be used
PendingDeletion Key scheduled for deletion
PendingImport Key pending import
Unavailable Key unavailable

Pricing

  • $1/month per CMK
  • $0.03 per 10,000 requests
  • $0.02 per 10,000 requests for cryptographic operations

Envelope Encryption

Envelope Encryption

Envelope encryption uses the CMK to encrypt a data key, then uses the data key to encrypt data.

Generate Data Key

# Generate data key
aws kms generate-data-key \
  --key-id alias/my-app-key \
  --key-spec AES_256 \
  --encryption-context purpose=database-encryption

# Response:
# Plaintext: base64-encoded data key
# CiphertextBlob: encrypted data key

Python Implementation

import boto3
import os
from cryptography.fernet import Fernet

kms = boto3.client('kms')

def encrypt_data(plaintext, key_id):
    # Generate data key
    response = kms.generate_data_key(
        KeyId=key_id,
        KeySpec='AES_256'
    )
    
    # Use plaintext key to encrypt data
    plaintext_key = response['Plaintext']
    f = Fernet(base64.urlsafe_b64encode(plaintext_key))
    encrypted_data = f.encrypt(plaintext.encode())
    
    # Store encrypted data key + encrypted data
    return {
        'encrypted_key': response['CiphertextBlob'],
        'encrypted_data': encrypted_data
    }

def decrypt_data(encrypted_key, encrypted_data, key_id):
    # Decrypt data key
    response = kms.decrypt(
        CiphertextBlob=encrypted_key,
        EncryptionContext={'purpose': 'database-encryption'}
    )
    
    # Use decrypted key to decrypt data
    plaintext_key = response['Plaintext']
    f = Fernet(base64.urlsafe_b64encode(plaintext_key))
    decrypted_data = f.decrypt(encrypted_data)
    
    return decrypted_data.decode()

Encryption Context

# Use encryption context for additional authenticated data
aws kms encrypt \
  --key-id alias/my-app-key \
  --plaintext "secret-data" \
  --encryption-context purpose=database,environment=prod

# Must provide same context to decrypt
aws kms decrypt \
  --ciphertext-blob <blob> \
  --encryption-context purpose=database,environment=prod

KMS Integration

KMS Integration

S3 Encryption

# Default encryption with KMS
aws s3api put-bucket-encryption \
  --bucket my-bucket \
  --server-side-encryption-configuration '{
    "Rules": [{
      "ApplyServerSideEncryptionByDefault": {
        "SSEAlgorithm": "aws:kms",
        "KMSMasterKeyID": "alias/my-s3-key"
      },
      "BucketKeyEnabled": true
    }]
  }'

# Encrypt specific object
aws s3 cp file.txt s3://my-bucket/file.txt \
  --sse aws:kms \
  --sse-kms-key-id alias/my-s3-key

EBS Encryption

# Enable default encryption
aws ec2 enable-ebs-encryption-by-default

# Create encrypted volume
aws ec2 create-volume \
  --availability-zone us-east-1a \
  --size 100 \
  --volume-type gp3 \
  --encrypted \
  --kms-key-id alias/my-ebs-key

RDS Encryption

# Create encrypted RDS instance
aws rds create-db-instance \
  --db-instance-identifier mydb \
  --storage-encrypted \
  --kms-key-id alias/my-rds-key

Lambda Environment Variables

# Encrypt environment variables
aws lambda update-function-configuration \
  --function-name my-function \
  --kms-key-arn arn:aws:kms:us-east-1:xxx:key/my-key

# Environment variables are encrypted at rest
# Lambda decrypts them automatically in the function

Key Policies and Grants

Key Policies and Grants

Key Policy

{
  "Version": "2012-10-17",
  "Id": "key-consolepolicy",
  "Statement": [
    {
      "Sid": "EnableRootAccountAccess",
      "Effect": "Allow",
      "Principal": {
        "AWS": "arn:aws:iam::123456789012:root"
      },
      "Action": "kms:*",
      "Resource": "*"
    },
    {
      "Sid": "AllowKeyAdministration",
      "Effect": "Allow",
      "Principal": {
        "AWS": "arn:aws:iam::123456789012:role/KeyAdministrator"
      },
      "Action": [
        "kms:Create*",
        "kms:Describe*",
        "kms:Enable*",
        "kms:List*",
        "kms:Put*",
        "kms:Update*",
        "kms:Revoke*",
        "kms:Disable*",
        "kms:Get*",
        "kms:Delete*"
      ],
      "Resource": "*"
    },
    {
      "Sid": "AllowKeyUsage",
      "Effect": "Allow",
      "Principal": {
        "AWS": "arn:aws:iam::123456789012:role/ApplicationRole"
      },
      "Action": [
        "kms:Encrypt",
        "kms:Decrypt",
        "kms:ReEncrypt*",
        "kms:GenerateDataKey*",
        "kms:DescribeKey"
      ],
      "Resource": "*"
    }
  ]
}

Grants

# Create a grant
aws kms create-grant \
  --key-id alias/my-app-key \
  --grantee-principal arn:aws:iam::xxx:role/my-role \
  --operations Encrypt Decrypt GenerateDataKey

# List grants
aws kms list-grants --key-id alias/my-app-key

# Revoke a grant
aws kms revoke-grant --key-id alias/my-app-key --grant-id xxx

KMS Best Practices and Security

KMS Best Practices and Security

Key Rotation

# Enable automatic annual rotation
aws kms enable-key-rotation --key-id key-id

# Check rotation status
aws kms get-key-rotation-status --key-id key-id

# Manual rotation (create new key, update alias)
aws kms create-key --description "New key"
aws kms update-alias --alias-name alias/my-key --target-key-id new-key-id

Key Management Best Practices

  1. Enable key rotation for all CMKs
  2. Use key policies instead of IAM policies for cross-account access
  3. Use encryption context for additional security
  4. Monitor KMS usage with CloudTrail
  5. Separate key administrators from key users
  6. Use grants for temporary access
  7. Disable unused keys instead of deleting
  8. Implement key deletion protection (30-day waiting period)

CloudTrail Logging

# KMS operations are logged in CloudTrail
aws cloudtrail lookup-events \
  --lookup-attributes AttributeKey=EventName,AttributeValue=Decrypt \
  --max-results 10

Cross-Account Access

# Allow another account to use the key
aws kms put-key-policy \
  --key-id key-id \
  --policy-name cross-account \
  --policy '{
    "Version": "2012-10-17",
    "Statement": [{
      "Sid": "CrossAccountAccess",
      "Effect": "Allow",
      "Principal": {
        "AWS": "arn:aws:iam::OTHER_ACCOUNT:root"
      },
      "Action": "kms:*",
      "Resource": "*"
    }]
  }'