Encryption at rest protects your data from unauthorized access even if storage media is physically compromised or accessed by unauthorized users. AWS provides multiple encryption options for S3, EBS, RDS, and other storage services, allowing you to meet compliance requirements and security best practices with minimal operational overhead.
This article is part of our comprehensive Cloud Security Tips for 2026 guide covering essential practices for protecting your cloud environment.
Understanding AWS Encryption Options
| Encryption Type | Key Management | Audit Capability | Cost |
|---|---|---|---|
| SSE-S3 | AWS Managed | Limited | Free |
| SSE-KMS | AWS KMS | Full CloudTrail | $1/key/month + API |
| SSE-C | Customer Managed | None (your keys) | Free |
| Client-Side | Customer Managed | Your implementation | Free |
S3 Bucket Encryption
Enable Default Encryption (SSE-S3)
# Enable SSE-S3 default encryption on bucket
aws s3api put-bucket-encryption \
--bucket my-secure-bucket \
--server-side-encryption-configuration '{
"Rules": [{
"ApplyServerSideEncryptionByDefault": {
"SSEAlgorithm": "AES256"
},
"BucketKeyEnabled": false
}]
}'
# Verify encryption configuration
aws s3api get-bucket-encryption --bucket my-secure-bucketEnable Default Encryption (SSE-KMS)
# Create KMS key for S3 encryption
aws kms create-key \
--description "S3 encryption key" \
--key-usage ENCRYPT_DECRYPT \
--key-spec SYMMETRIC_DEFAULT
# Get the key ID from response
KEY_ID="12345678-1234-1234-1234-123456789012"
# Create alias for easier management
aws kms create-alias \
--alias-name alias/s3-encryption \
--target-key-id $KEY_ID
# Enable SSE-KMS default encryption with S3 Bucket Keys
aws s3api put-bucket-encryption \
--bucket my-secure-bucket \
--server-side-encryption-configuration '{
"Rules": [{
"ApplyServerSideEncryptionByDefault": {
"SSEAlgorithm": "aws:kms",
"KMSMasterKeyID": "alias/s3-encryption"
},
"BucketKeyEnabled": true
}]
}'Terraform Configuration for S3 Encryption
resource "aws_kms_key" "s3_key" {
description = "S3 bucket encryption key"
deletion_window_in_days = 30
enable_key_rotation = true
}
resource "aws_kms_alias" "s3_key_alias" {
name = "alias/s3-encryption"
target_key_id = aws_kms_key.s3_key.key_id
}
resource "aws_s3_bucket" "secure_bucket" {
bucket = "my-secure-bucket"
}
resource "aws_s3_bucket_server_side_encryption_configuration" "secure_bucket" {
bucket = aws_s3_bucket.secure_bucket.id
rule {
apply_server_side_encryption_by_default {
kms_master_key_id = aws_kms_key.s3_key.arn
sse_algorithm = "aws:kms"
}
bucket_key_enabled = true
}
}
# Deny unencrypted uploads
resource "aws_s3_bucket_policy" "require_encryption" {
bucket = aws_s3_bucket.secure_bucket.id
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Sid = "DenyUnencryptedUploads"
Effect = "Deny"
Principal = "*"
Action = "s3:PutObject"
Resource = "${aws_s3_bucket.secure_bucket.arn}/*"
Condition = {
StringNotEquals = {
"s3:x-amz-server-side-encryption" = ["aws:kms", "AES256"]
}
}
}
]
})
}Upload with Client-Specified Encryption
# Upload with SSE-KMS and specific key
aws s3 cp document.pdf s3://my-secure-bucket/ \
--sse aws:kms \
--sse-kms-key-id alias/s3-encryption
# Upload with SSE-C (customer-provided key)
# Key must be base64-encoded 256-bit key
aws s3 cp document.pdf s3://my-secure-bucket/ \
--sse-c \
--sse-c-key $(openssl rand -base64 32)Encrypt Existing S3 Objects
Existing unencrypted objects must be copied to encrypt them:
# Copy single object with encryption
aws s3 cp s3://my-bucket/file.txt s3://my-bucket/file.txt \
--sse aws:kms \
--sse-kms-key-id alias/s3-encryption
# Encrypt all objects in bucket using S3 Batch Operations
# First, create inventory of unencrypted objects
aws s3api list-objects-v2 \
--bucket my-bucket \
--query 'Contents[?ServerSideEncryption==`null`].Key'Bulk Encryption with S3 Batch Operations
# Create manifest file listing objects to encrypt
# Then create batch job
aws s3control create-job \
--account-id 123456789012 \
--operation '{
"S3PutObjectCopy": {
"TargetResource": "arn:aws:s3:::my-bucket",
"StorageClass": "STANDARD",
"SSEAwsKmsKeyId": "arn:aws:kms:us-east-1:123456789012:key/12345678-1234-1234-1234-123456789012"
}
}' \
--manifest '{
"Spec": {"Format": "S3BatchOperations_CSV_20180820", "Fields": ["Bucket", "Key"]},
"Location": {"ObjectArn": "arn:aws:s3:::manifest-bucket/manifest.csv", "ETag": "abc123"}
}' \
--report '{
"Bucket": "arn:aws:s3:::report-bucket",
"Prefix": "batch-reports/",
"Format": "Report_CSV_20180820",
"Enabled": true
}' \
--priority 10 \
--role-arn arn:aws:iam::123456789012:role/S3BatchRoleEBS Volume Encryption
Enable Default EBS Encryption
# Enable default EBS encryption for the region
aws ec2 enable-ebs-encryption-by-default
# Verify default encryption is enabled
aws ec2 get-ebs-encryption-by-default
# Set default KMS key for EBS
aws ec2 modify-ebs-default-kms-key-id \
--kms-key-id alias/ebs-encryption
# Get current default key
aws ec2 get-ebs-default-kms-key-idCreate Encrypted EBS Volume
# Create encrypted volume with default key
aws ec2 create-volume \
--availability-zone us-east-1a \
--size 100 \
--volume-type gp3 \
--encrypted
# Create encrypted volume with specific KMS key
aws ec2 create-volume \
--availability-zone us-east-1a \
--size 100 \
--volume-type gp3 \
--encrypted \
--kms-key-id alias/ebs-encryptionEncrypt Existing Unencrypted Volume
# Step 1: Create snapshot of unencrypted volume
aws ec2 create-snapshot \
--volume-id vol-0123456789abcdef0 \
--description "Snapshot for encryption"
# Step 2: Copy snapshot with encryption
aws ec2 copy-snapshot \
--source-region us-east-1 \
--source-snapshot-id snap-0123456789abcdef0 \
--destination-region us-east-1 \
--encrypted \
--kms-key-id alias/ebs-encryption \
--description "Encrypted copy"
# Step 3: Create new volume from encrypted snapshot
aws ec2 create-volume \
--snapshot-id snap-encrypted123456 \
--availability-zone us-east-1a \
--volume-type gp3
# Step 4: Attach new volume and migrate dataTerraform Configuration for EBS Encryption
resource "aws_ebs_encryption_by_default" "enabled" {
enabled = true
}
resource "aws_kms_key" "ebs_key" {
description = "EBS volume encryption key"
deletion_window_in_days = 30
enable_key_rotation = true
}
resource "aws_ebs_default_kms_key" "default" {
key_arn = aws_kms_key.ebs_key.arn
}
resource "aws_ebs_volume" "encrypted_volume" {
availability_zone = "us-east-1a"
size = 100
type = "gp3"
encrypted = true
kms_key_id = aws_kms_key.ebs_key.arn
tags = {
Name = "encrypted-data-volume"
}
}RDS Database Encryption
Create Encrypted RDS Instance
# Create encrypted RDS instance
aws rds create-db-instance \
--db-instance-identifier my-encrypted-db \
--db-instance-class db.t3.medium \
--engine postgres \
--engine-version 15.4 \
--master-username admin \
--master-user-password "SecurePassword123!" \
--allocated-storage 100 \
--storage-encrypted \
--kms-key-id alias/rds-encryption \
--vpc-security-group-ids sg-12345678 \
--db-subnet-group-name my-subnet-groupEncrypt Existing RDS Instance
# Step 1: Create snapshot
aws rds create-db-snapshot \
--db-instance-identifier my-unencrypted-db \
--db-snapshot-identifier pre-encryption-snapshot
# Step 2: Copy snapshot with encryption
aws rds copy-db-snapshot \
--source-db-snapshot-identifier pre-encryption-snapshot \
--target-db-snapshot-identifier encrypted-snapshot \
--kms-key-id alias/rds-encryption
# Step 3: Restore encrypted instance
aws rds restore-db-instance-from-db-snapshot \
--db-instance-identifier my-encrypted-db \
--db-snapshot-identifier encrypted-snapshot \
--db-instance-class db.t3.medium
# Step 4: Update application connection strings
# Step 5: Delete old unencrypted instance after verificationTerraform Configuration for RDS Encryption
resource "aws_kms_key" "rds_key" {
description = "RDS encryption key"
deletion_window_in_days = 30
enable_key_rotation = true
}
resource "aws_db_instance" "encrypted_db" {
identifier = "my-encrypted-db"
engine = "postgres"
engine_version = "15.4"
instance_class = "db.t3.medium"
allocated_storage = 100
storage_type = "gp3"
username = "admin"
password = var.db_password
storage_encrypted = true
kms_key_id = aws_kms_key.rds_key.arn
vpc_security_group_ids = [aws_security_group.db.id]
db_subnet_group_name = aws_db_subnet_group.main.name
backup_retention_period = 7
skip_final_snapshot = false
final_snapshot_identifier = "my-encrypted-db-final"
}KMS Key Management Best Practices
Enable Key Rotation
# Enable automatic key rotation (annually)
aws kms enable-key-rotation --key-id alias/my-encryption-key
# Verify rotation is enabled
aws kms get-key-rotation-status --key-id alias/my-encryption-keyCreate Key Policy for Separation of Duties
aws kms put-key-policy \
--key-id alias/my-encryption-key \
--policy-name default \
--policy '{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "Enable IAM Policies",
"Effect": "Allow",
"Principal": {"AWS": "arn:aws:iam::123456789012:root"},
"Action": "kms:*",
"Resource": "*"
},
{
"Sid": "Allow Key Administrators",
"Effect": "Allow",
"Principal": {"AWS": "arn:aws:iam::123456789012:role/KeyAdminRole"},
"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"
],
"Resource": "*"
},
{
"Sid": "Allow Key Usage",
"Effect": "Allow",
"Principal": {"AWS": "arn:aws:iam::123456789012:role/ApplicationRole"},
"Action": [
"kms:Encrypt",
"kms:Decrypt",
"kms:GenerateDataKey*",
"kms:DescribeKey"
],
"Resource": "*"
}
]
}'Audit Encryption Status
#!/bin/bash
# Encryption audit script
echo "=== AWS Encryption Audit ==="
# Check S3 buckets
echo -e "\n1. Unencrypted S3 Buckets:"
for bucket in $(aws s3api list-buckets --query 'Buckets[].Name' --output text); do
ENCRYPTION=$(aws s3api get-bucket-encryption --bucket "$bucket" 2>&1)
if [[ "$ENCRYPTION" == *"ServerSideEncryptionConfigurationNotFoundError"* ]]; then
echo " UNENCRYPTED: $bucket"
fi
done
# Check EBS volumes
echo -e "\n2. Unencrypted EBS Volumes:"
aws ec2 describe-volumes \
--filters Name=encrypted,Values=false \
--query 'Volumes[*].[VolumeId,Size,State]' \
--output table
# Check RDS instances
echo -e "\n3. Unencrypted RDS Instances:"
aws rds describe-db-instances \
--query 'DBInstances[?StorageEncrypted==`false`].[DBInstanceIdentifier,Engine,DBInstanceClass]' \
--output table
# Check default EBS encryption
echo -e "\n4. Default EBS Encryption Status:"
aws ec2 get-ebs-encryption-by-default
echo -e "\n=== Audit Complete ==="Best Practices Summary
| Practice | Recommendation |
|---|---|
| S3 Buckets | Enable SSE-KMS with S3 Bucket Keys by default |
| EBS Volumes | Enable default encryption in all regions |
| RDS Databases | Always create with encryption enabled |
| KMS Keys | Enable automatic annual rotation |
| Key Policies | Separate admin and usage permissions |
| Monitoring | Audit unencrypted resources monthly |