AWS KMS for S3 Encryption: AWS Managed Key vs. CMK — A Decision Guide
Encrypting data at rest in S3 is non-negotiable for any production workload, but the moment you open the AWS KMS console, you face a fork in the road: use an AWS Managed Key and let AWS handle everything, or provision a Customer Managed Key (CMK) and take full control. The wrong choice costs you either operational flexibility or real money — sometimes both.
TL;DR — Quick Decision Matrix
| Dimension | AWS Managed Key (aws/s3) | Customer Managed Key (CMK) |
|---|---|---|
| Who creates it? | AWS (automatically) | You |
| Key policy control | None — AWS-defined policy | Full — you author the key policy |
| Key rotation | Automatic (every ~3 years, AWS-managed) | Optional automatic annual rotation, or manual |
| Cross-account access | Not supported | Supported via key policy |
| Cross-region replication encryption | Limited | Fully supported |
| Audit via CloudTrail | Yes (limited detail) | Yes (full cryptographic API detail) |
| Cost | No additional KMS charge for key storage; API call charges apply | Monthly key storage fee + API call charges (check AWS KMS Pricing) |
| Use when | Internal, single-account, low-compliance workloads | Regulated data, cross-account, fine-grained access control |
How AWS KMS Encryption Works with S3
Before choosing a key type, understand the data flow. S3 uses envelope encryption: your object is encrypted with a unique data key, and that data key is itself encrypted by your KMS key. The plaintext data key never persists on disk.
Encrypted Data Key S3->>S3: Encrypt object with
Plaintext Data Key (AES-256) S3->>S3: Discard Plaintext Data Key S3->>Store: Store Encrypted Object +
Encrypted Data Key (metadata) S3-->>App: 200 OK Note over App,Store: --- Retrieval Flow --- App->>S3: GetObject S3->>KMS: Decrypt(EncryptedDataKey) KMS-->>S3: Plaintext Data Key S3->>S3: Decrypt object S3-->>App: Plaintext Object
- PutObject request: Your application sends an object to S3 with a request to use SSE-KMS.
- GenerateDataKey API call: S3 calls KMS's
GenerateDataKeyAPI, specifying your KMS key ARN. - KMS responds: KMS returns a plaintext data key and an encrypted copy of that data key (encrypted under your KMS key).
- S3 encrypts the object: S3 uses the plaintext data key to encrypt your object using AES-256, then immediately discards the plaintext key from memory.
- Storage: S3 stores the encrypted object alongside the encrypted data key as object metadata.
- GetObject (decryption): On retrieval, S3 calls KMS's
DecryptAPI with the encrypted data key. KMS returns the plaintext data key, S3 decrypts the object, and serves it to the authorized caller.
Analogy — The Safety Deposit Box: Think of your KMS key as the bank's master key that never leaves the vault. Your data key is a unique box key made for each item you store. The bank (S3) uses the box key to lock your valuables, then locks the box key itself inside the vault. To retrieve your item, the bank must ask the vault (KMS) to unlock the box key first — and every such request is logged.
Deep Dive: AWS Managed Key vs. CMK
AWS Managed Key (aws/s3)
When you enable SSE-KMS on an S3 bucket without specifying a key, AWS creates and manages a key with the alias aws/s3 in your account. You cannot view or modify its key policy, cannot use it for cross-account operations, and cannot schedule its deletion. It is the "set it and forget it" option.
- ✅ Zero operational overhead
- ✅ No monthly key storage fee
- ❌ No key policy customization
- ❌ Cannot restrict or grant access at the key level
- ❌ Cannot be used cross-account
Customer Managed Key (CMK)
A CMK is a KMS key you create, own, and manage. You define the key policy (who can administer vs. use the key), enable or disable the key, set rotation schedules, and can grant cross-account access. This is the correct choice for any regulated, multi-account, or audit-intensive workload.
- ✅ Full key policy control (least-privilege access)
- ✅ Cross-account and cross-region replication support
- ✅ Granular CloudTrail audit logs per cryptographic operation
- ✅ Key disablement and deletion scheduling
- ❌ Monthly key storage cost per key
- ❌ Requires IAM + key policy management discipline
KMS Cost Model — What You Actually Pay For
KMS pricing has two components. Always verify current rates at the official AWS KMS Pricing page as these figures change.
| Cost Component | AWS Managed Key | Customer Managed Key |
|---|---|---|
| Key storage (per key/month) | No charge | Charged (see AWS pricing page) |
| API requests | Charged per 10,000 requests | Charged per 10,000 requests |
| Free tier | AWS Free Tier includes a monthly API request allowance | Same API free tier applies |
Cost implication for S3: Every PutObject and GetObject on an SSE-KMS encrypted object triggers a KMS API call. For high-throughput buckets (millions of requests/day), KMS API costs can become significant regardless of key type. Use S3 Bucket Keys to dramatically reduce KMS API calls by generating a short-lived bucket-level data key, reducing per-object KMS calls by up to 99%.
Architecture: CMK-Encrypted S3 with Least-Privilege IAM
(Key Administration Only)"] AppRole["AppEC2Role
(GenerateDataKey + Decrypt Only)"] end subgraph "KMS" CMK["Customer Managed Key
alias/s3-production-cmk"] KeyPolicy["Key Policy
(Least Privilege)"] end subgraph "S3" Bucket["my-production-bucket
(SSE-KMS + Bucket Key Enabled)"] BucketPolicy["Bucket Policy
(Deny non-CMK uploads)"] end CloudTrail["AWS CloudTrail
(KMS API Audit Logs)"] end AdminRole -->|"Manages key lifecycle
(no Decrypt)"| CMK AppRole -->|"GenerateDataKey
Decrypt"| CMK CMK --> KeyPolicy Bucket --> BucketPolicy AppRole -->|"PutObject / GetObject"| Bucket Bucket <-->|"Envelope Encryption
via Bucket Key"| CMK CMK -.->|"All API calls logged"| CloudTrail
- Application Role: An EC2 or Lambda execution role is granted
kms:GenerateDataKeyandkms:Decryptin the CMK key policy — nothing more. - Admin Role: A separate administrator role holds
kms:Create*,kms:Describe*,kms:Enable*,kms:List*,kms:Put*,kms:Update*,kms:Revoke*,kms:Disable*,kms:Delete*,kms:ScheduleKeyDeletion,kms:CancelKeyDeletion— but NOTkms:Decryptorkms:GenerateDataKey. This enforces separation of duties. - S3 Bucket Policy: Enforces that all uploads must use SSE-KMS with the specific CMK ARN.
- CloudTrail: Every KMS API call is logged, providing a full audit trail of who decrypted what and when.
Implementation: Step-by-Step
Step 1: Create a Customer Managed Key
aws kms create-key \
--description "CMK for my-production-bucket S3 encryption" \
--key-usage ENCRYPT_DECRYPT \
--origin AWS_KMS \
--region us-east-1
aws kms create-alias \
--alias-name alias/s3-production-cmk \
--target-key-id <key-id-from-above> \
--region us-east-1
Step 2: Apply a Least-Privilege Key Policy
🔽 [Click to expand] — CMK Key Policy JSON
{
"Version": "2012-10-17",
"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/KMSAdminRole"
},
"Action": [
"kms:Create*",
"kms:Describe*",
"kms:Enable*",
"kms:List*",
"kms:Put*",
"kms:Update*",
"kms:Revoke*",
"kms:Disable*",
"kms:Delete*",
"kms:ScheduleKeyDeletion",
"kms:CancelKeyDeletion"
],
"Resource": "*"
},
{
"Sid": "AllowS3EncryptionOperations",
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::123456789012:role/AppEC2Role"
},
"Action": [
"kms:GenerateDataKey",
"kms:Decrypt"
],
"Resource": "*"
}
]
}
Step 3: Enable SSE-KMS with S3 Bucket Key on the Bucket
aws s3api put-bucket-encryption \
--bucket my-production-bucket \
--server-side-encryption-configuration '{
"Rules": [
{
"ApplyServerSideEncryptionByDefault": {
"SSEAlgorithm": "aws:kms",
"KMSMasterKeyID": "arn:aws:kms:us-east-1:123456789012:key/<your-key-id>"
},
"BucketKeyEnabled": true
}
]
}' \
--region us-east-1
Step 4: Enforce Encryption via Bucket Policy
A default encryption setting alone does not prevent a caller from explicitly uploading an object with a different encryption method or no encryption header. The following bucket policy closes all three loopholes: missing encryption header, wrong encryption algorithm, and wrong KMS key.
🔽 [Click to expand] — S3 Bucket Policy (Hardened Encryption Enforcement)
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "DenyUnencryptedObjectUploads",
"Effect": "Deny",
"Principal": "*",
"Action": "s3:PutObject",
"Resource": "arn:aws:s3:::my-production-bucket/*",
"Condition": {
"Null": {
"s3:x-amz-server-side-encryption": "true"
}
}
},
{
"Sid": "DenyIncorrectEncryptionHeader",
"Effect": "Deny",
"Principal": "*",
"Action": "s3:PutObject",
"Resource": "arn:aws:s3:::my-production-bucket/*",
"Condition": {
"StringNotEquals": {
"s3:x-amz-server-side-encryption": "aws:kms"
}
}
},
{
"Sid": "DenyWrongKMSKey",
"Effect": "Deny",
"Principal": "*",
"Action": "s3:PutObject",
"Resource": "arn:aws:s3:::my-production-bucket/*",
"Condition": {
"ArnNotEquals": {
"s3:x-amz-server-side-encryption-aws-kms-key-id": "arn:aws:kms:us-east-1:123456789012:key/<your-key-id>"
}
}
}
]
}
What each statement does:
- DenyUnencryptedObjectUploads: Blocks any
PutObjectrequest where thex-amz-server-side-encryptionheader is absent entirely. - DenyIncorrectEncryptionHeader: Blocks requests that specify an encryption type other than
aws:kms(e.g.,AES256). - DenyWrongKMSKey: Blocks requests that use a valid KMS header but reference a different KMS key — ensuring only your designated CMK is used.
Step 5: Enable Key Rotation
aws kms enable-key-rotation \
--key-id <your-key-id> \
--region us-east-1
This enables automatic annual rotation of the CMK's backing cryptographic material. Existing ciphertext remains decryptable because KMS retains all previous key versions internally.
Decision Flowchart: Which Key Type Should You Use?
access to data?"} Q2{"Compliance requires
key control or audit?
(PCI, HIPAA, FedRAMP)"} Q3{"Need to disable/delete
key for data destruction?"} Q4{"Need cross-region
replication with encryption?"} UseCMK["✅ Use Customer
Managed Key (CMK)"] UseAWS["✅ Use AWS Managed Key
(aws/s3) — Lower Cost"] Start --> Q1 Q1 -->|"Yes"| UseCMK Q1 -->|"No"| Q2 Q2 -->|"Yes"| UseCMK Q2 -->|"No"| Q3 Q3 -->|"Yes"| UseCMK Q3 -->|"No"| Q4 Q4 -->|"Yes"| UseCMK Q4 -->|"No"| UseAWS
- If you need cross-account access to the encrypted data, you must use a CMK — AWS Managed Keys do not support cross-account key grants.
- If compliance frameworks (PCI-DSS, HIPAA, FedRAMP) require demonstrable key control and audit trails, use a CMK.
- If you need to disable or delete the key as part of a data destruction workflow, use a CMK — you cannot delete an AWS Managed Key.
- If none of the above apply and cost minimization is the priority, an AWS Managed Key is sufficient.
Glossary
| Term | Definition |
|---|---|
| SSE-KMS | Server-Side Encryption with AWS Key Management Service. S3 encrypts objects at rest using KMS-managed keys. |
| Envelope Encryption | A pattern where a data key encrypts the plaintext data, and a master key (KMS key) encrypts the data key. Only the encrypted data key is stored alongside the data. |
| S3 Bucket Key | A short-lived, bucket-level key generated by KMS that S3 uses to encrypt individual object data keys locally, reducing the number of direct KMS API calls and associated costs. |
| Key Policy | A resource-based policy attached directly to a KMS key that defines who can administer and use the key. Key policies are the primary access control mechanism for KMS keys. |
| Key Rotation | The process of replacing the backing cryptographic material of a KMS key on a schedule. For CMKs, AWS can rotate the material annually while preserving the key ID and alias. |
Next Steps
- 📖 AWS KMS Developer Guide
- 📖 Using SSE-KMS with S3 — Official Docs
- 📖 S3 Bucket Keys — Reducing KMS Costs
- 📖 AWS KMS Pricing
- 🔒 Review your existing S3 buckets with S3 Security Best Practices and enable default bucket encryption as a baseline control.
Comments
Post a Comment