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
- Enable key rotation for all CMKs
- Use key policies instead of IAM policies for cross-account access
- Use encryption context for additional security
- Monitor KMS usage with CloudTrail
- Separate key administrators from key users
- Use grants for temporary access
- Disable unused keys instead of deleting
- 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": "*"
}]
}'