Data Encryption and Key Management
In this lesson, we'll explore how to protect your data at rest and in transit using AWS encryption services. You'll learn to implement encryption across various AWS services and manage cryptographic keys securely.
By the end of this lesson, you'll be able to:
- Understand encryption concepts and AWS Key Management Service (KMS)
- Implement encryption for S3, EBS, and RDS
- Manage encryption keys and understand key policies
- Use envelope encryption for large datasets
Understanding AWS Encryption Services
AWS provides multiple layers of encryption to protect your data:
- AWS Key Management Service (KMS): Managed service for creating and controlling encryption keys
- CloudHSM: Dedicated hardware security modules for regulatory compliance
- Server-side encryption: Automatic encryption by AWS services
- Client-side encryption: Encryption before sending data to AWS
AWS Key Management Service (KMS)
KMS is the cornerstone of AWS encryption. It provides secure key storage and cryptographic operations.
Creating a Customer Master Key (CMK)
import boto3
def create_kms_key(description):
kms_client = boto3.client('kms')
response = kms_client.create_key(
Description=description,
KeyUsage='ENCRYPT_DECRYPT',
Origin='AWS_KMS'
)
print(f"Created KMS Key: {response['KeyMetadata']['KeyId']}")
return response['KeyMetadata']['KeyId']
# Create a KMS key for application encryption
key_id = create_kms_key("Application Database Encryption Key")
Always use descriptive key names and enable key rotation for production workloads. AWS automatically rotates KMS keys every year when rotation is enabled.
Encrypting and Decrypting Data
import boto3
import base64
def encrypt_data(plaintext, key_id):
kms_client = boto3.client('kms')
response = kms_client.encrypt(
KeyId=key_id,
Plaintext=plaintext.encode('utf-8')
)
return base64.b64encode(response['CiphertextBlob']).decode('utf-8')
def decrypt_data(ciphertext_blob):
kms_client = boto3.client('kms')
response = kms_client.decrypt(
CiphertextBlob=base64.b64decode(ciphertext_blob)
)
return response['Plaintext'].decode('utf-8')
# Example usage
secret_data = "Sensitive application data"
encrypted = encrypt_data(secret_data, key_id)
print(f"Encrypted: {encrypted}")
decrypted = decrypt_data(encrypted)
print(f"Decrypted: {decrypted}")
Implementing Encryption Across AWS Services
S3 Bucket Encryption
import boto3
def create_encrypted_bucket(bucket_name, kms_key_id):
s3_client = boto3.client('s3')
# Create bucket with default encryption
s3_client.create_bucket(Bucket=bucket_name)
s3_client.put_bucket_encryption(
Bucket=bucket_name,
ServerSideEncryptionConfiguration={
'Rules': [
{
'ApplyServerSideEncryptionByDefault': {
'SSEAlgorithm': 'aws:kms',
'KMSMasterKeyID': kms_key_id
}
}
]
}
)
print(f"Created encrypted bucket: {bucket_name}")
# Create an encrypted S3 bucket
create_encrypted_bucket("my-encrypted-data-bucket", key_id)
EBS Volume Encryption
Resources:
EncryptedEC2Instance:
Type: AWS::EC2::Instance
Properties:
ImageId: ami-0c02fb55956c7d316
InstanceType: t3.micro
BlockDeviceMappings:
- DeviceName: /dev/sda1
Ebs:
Encrypted: true
KmsKeyId: alias/aws/ebs
VolumeSize: 8
VolumeType: gp3
RDS Database Encryption
-- RDS automatically encrypts the database when encryption is enabled
-- This is configured during instance creation
-- Example using AWS CLI:
aws rds create-db-instance \
--db-instance-identifier my-encrypted-db \
--db-instance-class db.t3.micro \
--engine mysql \
--master-username admin \
--master-user-password password123 \
--allocated-storage 20 \
--storage-encrypted \
--kms-key-id alias/aws/rds
Key Policies and Access Control
KMS keys have resource-based policies that control who can use them.
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "Enable IAM User Permissions",
"Effect": "Allow",
"Principal": {"AWS": "arn:aws:iam::123456789012:root"},
"Action": "kms:*",
"Resource": "*"
},
{
"Sid": "Allow access for Key Administrators",
"Effect": "Allow",
"Principal": {"AWS": "arn:aws:iam::123456789012:user/Alice"},
"Action": [
"kms:Create*",
"kms:Describe*",
"kms:Enable*",
"kms:List*",
"kms:Put*",
"kms:Update*",
"kms:Revoke*",
"kms:Disable*",
"kms:Get*",
"kms:Delete*",
"kms:TagResource",
"kms:UntagResource",
"kms:ScheduleKeyDeletion",
"kms:CancelKeyDeletion"
],
"Resource": "*"
}
]
}
Envelope Encryption
For large datasets, use envelope encryption where a data key is encrypted by KMS.
import boto3
from cryptography.fernet import Fernet
import base64
def encrypt_large_data(plaintext, kms_key_id):
kms_client = boto3.client('kms')
# Generate a data key
response = kms_client.generate_data_key(
KeyId=kms_key_id,
KeySpec='AES_256'
)
# Encrypt the data with the data key
fernet = Fernet(base64.b64encode(response['Plaintext']))
encrypted_data = fernet.encrypt(plaintext.encode('utf-8'))
# Return encrypted data and encrypted data key
return {
'encrypted_data': base64.b64encode(encrypted_data).decode('utf-8'),
'encrypted_data_key': base64.b64encode(response['CiphertextBlob']).decode('utf-8')
}
def decrypt_large_data(encrypted_data, encrypted_data_key):
kms_client = boto3.client('kms')
# Decrypt the data key
response = kms_client.decrypt(
CiphertextBlob=base64.b64decode(encrypted_data_key)
)
# Decrypt the data with the decrypted data key
fernet = Fernet(base64.b64encode(response['Plaintext']))
decrypted_data = fernet.decrypt(base64.b64decode(encrypted_data))
return decrypted_data.decode('utf-8')
# Example usage
large_data = "This is a large piece of data that needs efficient encryption..."
encrypted_result = encrypt_large_data(large_data, key_id)
decrypted_result = decrypt_large_data(
encrypted_result['encrypted_data'],
encrypted_result['encrypted_data_key']
)
print(f"Decrypted: {decrypted_result}")
Never store plaintext data keys with your encrypted data. Always encrypt data keys with KMS and store them separately from your encrypted data.
Common Pitfalls
- Forgetting key policies: KMS keys deny all access by default. Always configure proper key policies
- Mixing key types: AWS-managed keys vs. customer-managed keys have different capabilities and costs
- Ignoring key rotation: Manual key rotation requires re-encrypting all data, while automatic rotation doesn't
- Cost underestimation: KMS API calls incur charges; envelope encryption reduces costs for large datasets
- Regional limitations: KMS keys are region-specific; plan multi-region deployments accordingly
Summary
Data encryption in AWS is multi-layered and service-specific. KMS provides centralized key management, while individual AWS services offer built-in encryption capabilities. Remember to use envelope encryption for large datasets, configure proper key policies, and understand the cost implications of your encryption strategy.
Quiz
AWS KMS & Encryption Fundamentals
What is the primary advantage of using envelope encryption with KMS?