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.

sequenceDiagram participant App as "Application" participant S3 as "Amazon S3" participant KMS as "AWS KMS" participant Store as "S3 Storage" App->>S3: PutObject (SSE-KMS request) S3->>KMS: GenerateDataKey(KeyId=CMK_ARN) KMS-->>S3: Plaintext Data Key +
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
  1. PutObject request: Your application sends an object to S3 with a request to use SSE-KMS.
  2. GenerateDataKey API call: S3 calls KMS's GenerateDataKey API, specifying your KMS key ARN.
  3. KMS responds: KMS returns a plaintext data key and an encrypted copy of that data key (encrypted under your KMS key).
  4. 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.
  5. Storage: S3 stores the encrypted object alongside the encrypted data key as object metadata.
  6. GetObject (decryption): On retrieval, S3 calls KMS's Decrypt API 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

graph TD subgraph "AWS Account 123456789012" subgraph "IAM Layer" AdminRole["KMSAdminRole
(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
  1. Application Role: An EC2 or Lambda execution role is granted kms:GenerateDataKey and kms:Decrypt in the CMK key policy — nothing more.
  2. 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 NOT kms:Decrypt or kms:GenerateDataKey. This enforces separation of duties.
  3. S3 Bucket Policy: Enforces that all uploads must use SSE-KMS with the specific CMK ARN.
  4. 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 PutObject request where the x-amz-server-side-encryption header 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?

graph TD Start(["Encrypting an S3 Bucket?"]) Q1{"Need cross-account
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
  1. 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.
  2. If compliance frameworks (PCI-DSS, HIPAA, FedRAMP) require demonstrable key control and audit trails, use a CMK.
  3. 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.
  4. 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

Comments

Popular posts from this blog

EC2 No Internet Access in Custom VPC: Attaching an Internet Gateway and Fixing Route Tables

IAM User vs. IAM Role: Why Your EC2 Instance Should Never Use a User

Lambda Infinite Loop with S3: How to Prevent Recursive Triggers