The Complete AWS Key Management Service (KMS) Guide — CMKs, Envelope Encryption & Best Practice 2025
Imagine storing sensitive customer data—credit card numbers, health records, API secrets—completely unencrypted in the cloud. One misconfigured S3 bucket, one compromised credential, and exposure can be immediate and costly.
That’s exactly why AWS KMS exists.
If you’re building production workloads on AWS, encryption isn’t optional anymore. It’s table stakes. And AWS Key Management Service sits at the center of how encryption actually works across your entire cloud infrastructure.
In this aws kms tutorial, I’ll walk you through everything—from basic concepts to envelope encryption, key policies, and the real-world patterns I’ve seen work (and fail) in production environments. Whether you’re preparing for the AWS SAA-C03, Security Specialty, or DevOps Professional exam, or just trying to lock down your infrastructure properly, this guide has you covered.
Let’s dive in.
Table of Contents: AWS Key Management Service KMS
What Is AWS KMS and Why Should You Care?
AWS Key Management Service (KMS) is a managed service that lets you create, control, and manage cryptographic keys used to encrypt your data across AWS services.
Think of KMS keys like digital master locks protecting your cloud data. You control who can use those locks, when they can be used, and for what purpose.
Here’s the thing most people miss: KMS isn’t just about “turning on encryption.” It’s about centralized key management with fine-grained access control, automatic rotation, and full audit trails.
Every time you encrypt an S3 object, an RDS database, a Lambda environment variable, or an EBS volume—KMS is likely involved behind the scenes.
Common KMS use cases in DevOps:
- Encrypting S3 objects at rest with server-side encryption
- Protecting RDS database storage and automated backups
- Securing Lambda environment variables containing API keys
- Encrypting EBS volumes attached to EC2 instances
- Managing secrets in AWS Secrets Manager and SSM Parameter Store
- Signing container images and code artifacts
The beauty of KMS? You don’t handle raw cryptographic keys directly. AWS manages the hard parts—key storage, hardware security modules (HSMs), availability—while you focus on defining who gets access to what.
📚 Related Reading: If you’re new to IAM, check out our IAM Policies Deep Dive before continuing. Understanding IAM is essential for mastering KMS access control.
How AWS KMS Works: Architecture Deep Dive
Understanding the KMS architecture helps you make better decisions about key design and access patterns.
Control Plane vs Data Plane
KMS operates with two logical planes:
The Control Plane handles administrative operations—creating keys, setting policies, enabling rotation, managing aliases. These are the operations that define your key infrastructure.
The Data Plane handles cryptographic operations—encrypting, decrypting, generating data keys. These are the high-throughput operations your applications call constantly.
This separation matters for performance. Data plane operations are optimized for low latency and high availability. Control plane operations are less frequent but critical for security governance.
Types of KMS Keys
AWS KMS supports several key types, and choosing the right one depends on your use case:
AWS-managed keys are created and managed by AWS on your behalf. They’re free to store (you pay only for usage) and automatically rotate every year. You’ll see these with aliases like aws/s3, aws/rds, or aws/ebs. Simple, but limited control.
Customer-managed keys (CMKs) give you full control. You define key policies, manage rotation schedules, and can disable or delete keys. These cost $1/month per key plus API usage fees. For production workloads with compliance requirements, CMKs are usually the right choice.
Asymmetric keys support public-key cryptography for encryption or digital signing. Useful when you need to verify signatures outside AWS or share a public key with external parties.
HMAC keys generate and verify hash-based message authentication codes. Great for token validation, API request signing, and data integrity verification.
🎓 SAA-C03 Exam Tip: Know the difference between AWS-managed keys and customer-managed keys. Exam questions often test whether you understand when CMKs are required (cross-account access, custom rotation, key policy control).
Key Components You Need to Know
Every KMS key has several important attributes:
- Key ID: A unique identifier (UUID format) for the key
- Key ARN: The full Amazon Resource Name, used in IAM policies
- Key Material: The actual cryptographic material (either AWS-generated or imported)
- Key Policy: The resource-based policy attached directly to the key
- Aliases: Friendly names like
alias/my-app-keyfor easier reference - Grants: Temporary permissions for delegated access
Authentication vs Authorization
When your application calls kms:Decrypt, two things happen:
First, AWS verifies authentication—is the caller who they claim to be? This uses IAM credentials, STS tokens, or service roles.
Second, KMS checks authorization—does this authenticated identity have permission to perform this operation on this specific key? This involves evaluating both IAM policies and the key policy.
Both must pass. A mismatch anywhere means access denied.
Customer Managed Keys (CMKs) Explained
For anything beyond basic encryption needs, customer-managed keys are where you want to be.
Why? Because CMKs give you:
- Full control over key policies (who can use and administer the key)
- Custom rotation schedules (or disable rotation entirely for specific use cases)
- The ability to disable or schedule deletion
- Cross-account sharing capabilities
- Integration with CloudTrail for complete audit logging
AWS-managed keys vs Customer-managed keys
AWS-managed keys work fine for simple use cases. But they come with limitations—you can’t change the key policy, can’t share them across accounts, and can’t control rotation beyond the default annual schedule.
With CMKs, you’re in the driver’s seat. You decide exactly which IAM roles can encrypt, which can decrypt, and which can administer the key itself.
Key Lifecycle and States
CMKs move through several states:
- Enabled: Key is active and available for cryptographic operations
- Disabled: Key exists but cannot be used until re-enabled
- Pending Deletion: Key is scheduled for deletion (7-30 day waiting period)
- Pending Import: Key is created but awaiting external key material
Understanding these states matters for operational procedures. You can’t decrypt data if the key that encrypted it is disabled or deleted. Plan accordingly.
Automatic Key Rotation
For symmetric CMKs, you can enable automatic annual rotation. When rotation occurs, AWS generates new key material but keeps the same key ID and ARN. Old key material is retained indefinitely so you can still decrypt data encrypted before rotation.
Important clarification: AWS-managed keys rotate automatically every year. For customer-managed symmetric keys, you must explicitly enable rotation—it’s not on by default. Asymmetric keys and HMAC keys don’t support automatic rotation.
For more details, see the AWS KMS Key Rotation documentation.
Real Example: Creating a KMS Key for S3 and CloudTrail
Here’s how I typically set up a CMK for encrypting both S3 buckets and CloudTrail logs:
# Create the key with descriptive tags
aws kms create-key \
--description "Production encryption key for S3 and CloudTrail" \
--tags TagKey=Environment,TagValue=Production TagKey=Owner,TagValue=SecurityTeam \
--query 'KeyMetadata.KeyId' --output text
# Create a human-friendly alias
aws kms create-alias \
--alias-name alias/prod-encryption-key \
--target-key-id <key-id-from-above>
# Enable automatic rotation
aws kms enable-key-rotation --key-id alias/prod-encryption-key
The alias makes it much easier to reference in Terraform, CloudFormation, or application code.
🤔 Reflection Prompt:
Do you need full control over key policies and rotation, or will AWS-managed keys be enough for your workload? Consider compliance requirements, multi-account architectures, and operational overhead.
KMS Policies, Key Policies & Grants
This is where most people get confused. KMS has two layers of authorization: key policies and IAM policies. Both matter.
Key Policies Are Mandatory
Every KMS key has exactly one key policy attached directly to it. This policy is the primary access control mechanism.
Unlike S3 bucket policies (which are optional), key policies are required. If you don’t specify one, AWS creates a default policy that gives the root account full access.
Key Policy Structure
A key policy looks like a standard IAM policy but is attached to the key itself:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "Allow administration of the key",
"Effect": "Allow",
"Principal": {"AWS": "arn:aws:iam::111122223333:role/KeyAdminRole"},
"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": "Allow use of the key for encryption",
"Effect": "Allow",
"Principal": {"AWS": "arn:aws:iam::111122223333:role/AppEncryptionRole"},
"Action": [
"kms:Encrypt",
"kms:Decrypt",
"kms:GenerateDataKey*"
],
"Resource": "*"
}
]
}
Notice the separation: one statement for key administrators, another for key users. This is the principle of least privilege in action.
Key Policy vs IAM Policy
Here’s the critical rule: by default, IAM policies alone are not enough to grant KMS access.
The key policy must either grant access directly OR include a statement allowing IAM policies to grant access. That’s why the default key policy includes this statement:
{
"Sid": "Enable IAM policies",
"Effect": "Allow",
"Principal": {"AWS": "arn:aws:iam::111122223333:root"},
"Action": "kms:*",
"Resource": "*"
}
Without this, IAM policies can’t grant any KMS permissions—even if they say "Allow".
Policy Evaluation Logic
The evaluation order matters:
- If the key policy explicitly denies → Access denied
- If the key policy allows AND IAM policy allows → Access granted
- If only one allows (without the “Enable IAM policies” statement) → Access denied
Explicit deny always wins, regardless of where it appears.
Grants for Temporary Access
Grants provide a way to delegate permissions without modifying the key policy. They’re perfect for:
- Allowing AWS services to use keys on your behalf
- Temporary access that automatically expires
- Cross-service encryption workflows
When you encrypt an EBS volume, AWS creates a grant behind the scenes allowing the EC2 service to decrypt the volume on boot.
📝 Quiz Check:
What happens if an IAM policy allowskms:Decryptbut the key policy explicitly denies access for that principal?
Click to reveal answerAnswer: Access is denied. Explicit deny in any policy always takes precedence over any allow statement. This is fundamental to AWS policy evaluation—deny wins.
Envelope Encryption: The Heart of KMS
This is the concept that trips up most people studying for AWS certifications—but once it clicks, everything makes sense.
The Problem with Direct Encryption
You could theoretically send all your data to KMS for encryption. But that creates two problems:
- Performance: Network round-trips for every encryption operation add latency
- Data size limits: KMS can only encrypt up to 4,096 bytes (4 KB) directly via the
EncryptAPI (see AWS KMS Encrypt API documentation)
The Envelope Encryption Solution
Envelope encryption solves both problems elegantly:
Step 1: Your application calls kms:GenerateDataKey. KMS returns two things—a plaintext data key and an encrypted copy of that same key (encrypted with your CMK).
Step 2: Your application uses the plaintext data key to encrypt your actual data locally. This happens on your server, not in KMS.
Step 3: Your application stores the encrypted data alongside the encrypted data key (called the ciphertext blob).
Step 4: Immediately discard the plaintext data key from memory. Never store it.
Decryption Flow
When you need to read the data:
Step 1: Retrieve the encrypted data and the encrypted data key from storage.
Step 2: Call kms:Decrypt with the encrypted data key. KMS returns the plaintext data key.
Step 3: Use the plaintext data key to decrypt your data locally.
Step 4: Discard the plaintext data key from memory.

GenerateDataKeyWithoutPlaintext: When to Use It
There’s an alternative API—GenerateDataKeyWithoutPlaintext—that returns only the encrypted data key, never the plaintext. Why would you want this?
Use it when your application generates the key but a different service or system will perform the actual encryption/decryption. This minimizes plaintext key exposure in your application tier. For example, if you’re setting up encryption for a downstream service that will handle decryption itself, you never need to see the plaintext key.
For details, see the GenerateDataKeyWithoutPlaintext API documentation.
Hands-On Lab: Envelope Encryption with CLI and Python
Let’s walk through a complete envelope encryption workflow. This is the kind of hands-on practice that cements understanding.
Step 1: Generate a Data Key (CLI)
# Generate a data key and capture both plaintext and encrypted versions
aws kms generate-data-key \
--key-id alias/prod-encryption-key \
--key-spec AES_256 \
--output json > datakey_response.json
# Extract the plaintext key (base64 encoded)
cat datakey_response.json | jq -r '.Plaintext' | base64 --decode > plaintext_datakey.bin
# Extract the encrypted key (for storage)
cat datakey_response.json | jq -r '.CiphertextBlob' | base64 --decode > encrypted_datakey.bin
# Verify key sizes
ls -la *.bin
# plaintext_datakey.bin should be 32 bytes (256 bits)
# encrypted_datakey.bin will be larger due to KMS metadata
Step 2: Encrypt Data Locally (Python)
import boto3
import base64
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
import os
def envelope_encrypt(plaintext_data: bytes, key_alias: str) -> dict:
"""
Encrypt data using envelope encryption pattern.
Returns encrypted data and encrypted data key for storage.
"""
kms = boto3.client('kms')
# Step 1: Get data key from KMS
response = kms.generate_data_key(
KeyId=key_alias,
KeySpec='AES_256'
)
plaintext_key = response['Plaintext'] # 32 bytes for AES-256
encrypted_key = response['CiphertextBlob'] # Store this with your data
# Step 2: Encrypt data locally using AES-GCM
# Generate a random 96-bit nonce (required for AES-GCM)
nonce = os.urandom(12)
# Create cipher and encrypt
aesgcm = AESGCM(plaintext_key)
ciphertext = aesgcm.encrypt(nonce, plaintext_data, None)
# Step 3: CRITICAL - Clear plaintext key from memory
# In production, use secure memory wiping
del plaintext_key
# Return everything needed for decryption
return {
'encrypted_data': base64.b64encode(nonce + ciphertext).decode(),
'encrypted_key': base64.b64encode(encrypted_key).decode()
}
def envelope_decrypt(encrypted_payload: dict) -> bytes:
"""
Decrypt data using envelope encryption pattern.
"""
kms = boto3.client('kms')
# Decode the stored values
encrypted_key = base64.b64decode(encrypted_payload['encrypted_key'])
encrypted_blob = base64.b64decode(encrypted_payload['encrypted_data'])
# Extract nonce (first 12 bytes) and ciphertext
nonce = encrypted_blob[:12]
ciphertext = encrypted_blob[12:]
# Step 1: Decrypt the data key using KMS
response = kms.decrypt(CiphertextBlob=encrypted_key)
plaintext_key = response['Plaintext']
# Step 2: Decrypt data locally
aesgcm = AESGCM(plaintext_key)
plaintext_data = aesgcm.decrypt(nonce, ciphertext, None)
# Step 3: Clear plaintext key
del plaintext_key
return plaintext_data
# Usage example
if __name__ == "__main__":
# Encrypt sensitive user data
user_pii = b'{"ssn": "123-45-6789", "dob": "1990-01-15"}'
encrypted = envelope_encrypt(user_pii, 'alias/user-pii-key')
print(f"Encrypted key length: {len(encrypted['encrypted_key'])}")
print(f"Encrypted data length: {len(encrypted['encrypted_data'])}")
# Later, decrypt it
decrypted = envelope_decrypt(encrypted)
print(f"Decrypted: {decrypted.decode()}")
Step 3: Decrypt the Data Key (CLI)
# When you need to decrypt, first recover the plaintext data key
aws kms decrypt \
--ciphertext-blob fileb://encrypted_datakey.bin \
--query 'Plaintext' \
--output text | base64 --decode > recovered_plaintext_key.bin
# Verify it matches the original
diff plaintext_datakey.bin recovered_plaintext_key.bin
# Should show no difference
# IMPORTANT: Delete plaintext keys after use
rm -f plaintext_datakey.bin recovered_plaintext_key.bin
💡 How I Use This in Production:
In one project, we encrypted patient health records before storing them in S3. The envelope encryption pattern let us process thousands of records per second locally while only making KMS calls for key generation and decryption. Our KMS costs stayed under $50/month even with millions of encrypted objects.
Cross-Account KMS Access Patterns
In multi-account AWS environments, sharing KMS keys across accounts is common—and requires careful configuration on both sides.
The Two-Sided Handshake
Cross-account KMS access requires permissions in two places:
- The key policy in the key-owning account must allow the external account
- The IAM policy in the consuming account must allow the principal to use the key
Step 1: Key Policy in Account A (Key Owner)
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowCrossAccountUse",
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::999988887777:root"
},
"Action": [
"kms:Encrypt",
"kms:Decrypt",
"kms:ReEncrypt*",
"kms:GenerateDataKey*",
"kms:DescribeKey"
],
"Resource": "*"
},
{
"Sid": "AllowCrossAccountGrant",
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::999988887777:root"
},
"Action": [
"kms:CreateGrant",
"kms:ListGrants",
"kms:RevokeGrant"
],
"Resource": "*",
"Condition": {
"Bool": {
"kms:GrantIsForAWSResource": "true"
}
}
}
]
}
Step 2: IAM Role and Policy in Account B (Consumer)
First, create an IAM role with a trust policy:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": "lambda.amazonaws.com"
},
"Action": "sts:AssumeRole"
}
]
}
Then attach an IAM policy allowing KMS operations on the cross-account key:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowCrossAccountKMSUse",
"Effect": "Allow",
"Action": [
"kms:Encrypt",
"kms:Decrypt",
"kms:GenerateDataKey*"
],
"Resource": "arn:aws:kms:us-east-1:111122223333:key/12345678-1234-1234-1234-123456789012"
}
]
}
Step 3: CLI Sequence to Test Cross-Account Access
From Account B, assuming the role and testing encryption:
# Assume the role in Account B
export $(printf "AWS_ACCESS_KEY_ID=%s AWS_SECRET_ACCESS_KEY=%s AWS_SESSION_TOKEN=%s" \
$(aws sts assume-role \
--role-arn arn:aws:iam::999988887777:role/CrossAccountKMSRole \
--role-session-name test-session \
--query "Credentials.[AccessKeyId,SecretAccessKey,SessionToken]" \
--output text))
# Test encryption using Account A's key
echo "test data" | aws kms encrypt \
--key-id arn:aws:kms:us-east-1:111122223333:key/12345678-1234-1234-1234-123456789012 \
--plaintext fileb:///dev/stdin \
--query CiphertextBlob \
--output text
# If successful, you'll see base64-encoded ciphertext
KMS Best Practices for Production
After years of working with KMS in production, here’s what actually matters:
Enable automatic key rotation for all symmetric CMKs. AWS rotates the key material annually while keeping the same key ID, so your applications don’t need changes. One-line configuration that significantly improves security posture.
Use CMKs for sensitive data, not AWS-managed keys. The extra dollar per month gives you control over policies, cross-account access, and compliance audit trails.
Never store plaintext data keys—anywhere. Not in logs, not in databases, not in environment variables. Generate, use, discard.
Integrate KMS with CloudTrail and actually review the logs. Every Encrypt, Decrypt, and GenerateDataKey call is recorded. Set up alerts for unusual patterns.
Use Grants instead of overly permissive policies. Need temporary access? Grants can be revoked instantly without modifying key policies.
Separate key administrators from key users. The person who can delete a key shouldn’t necessarily be able to decrypt data with it.
Tag keys using a defined strategy. Tags like Environment, CostCenter, DataClassification, and Owner make governance and cost allocation much easier.
Use multi-region keys for disaster recovery. If your DR strategy involves another region, multi-region keys ensure encrypted data remains accessible after failover.
🚀 Security Tip:
Avoid embedding encrypted values directly in EC2 user data scripts. Instead, store sensitive values in SSM Parameter Store as SecureString and retrieve them at runtime. This prevents secrets from appearing in instance metadata or CloudFormation templates.
Related: SSM Parameter Store Best Practices
KMS Monitoring, Logging & Incident Response
You can’t secure what you can’t see. Here’s how to maintain visibility into KMS operations—and what to do when things go wrong.
CloudTrail Integration
Every KMS API call generates a CloudTrail event. Key events to monitor:
Decryptcalls from unexpected principalsDisableKeyorScheduleKeyDeletioneventsCreateGrantoperations (could indicate privilege escalation)PutKeyPolicychanges (policy modifications)
💡 Tip — CloudTrail + CloudWatch Alert:
Create CloudWatch metric filters for KMS CloudTrail events likeDecrypt,DisableKey, andScheduleKeyDeletion, and alert on spikes or cross-account decrypt events. See KMS CloudTrail logging documentation.
AWS Config Rules
Enable Config rules to enforce KMS compliance:
kms-cmk-not-scheduled-for-deletion— Alert on keys pending deletioncmk-backing-key-rotation-enabled— Ensure rotation is enabled- Custom rules for key policy requirements
CloudWatch Alarms
Set up alarms for:
- High error rates on decrypt operations (could indicate attacks or misconfigurations)
- Unusual API call volumes
- Cross-account access patterns
GuardDuty Findings
GuardDuty can detect anomalous KMS usage patterns, including:
- Access from unusual IP addresses or locations
- API calls from known malicious infrastructure
- Potential credential compromise scenarios
Incident Response Runbook: KMS Issues
When KMS problems occur in production, having a runbook ready saves critical time.
Scenario 1: Key Accidentally Disabled
Symptoms: Applications throwing DisabledException errors, decryption failures.
Response:
# 1. Identify the disabled key
aws kms list-keys --query 'Keys[*].KeyId' --output text | \
xargs -I {} aws kms describe-key --key-id {} \
--query 'KeyMetadata.[KeyId,KeyState]' --output text | grep -i disabled
# 2. Re-enable the key (requires kms:EnableKey permission)
aws kms enable-key --key-id <key-id>
# 3. Verify
aws kms describe-key --key-id <key-id> --query 'KeyMetadata.KeyState'
# Should return "Enabled"
Scenario 2: Unexpected Decrypt Spikes
Symptoms: CloudWatch showing 10x normal decrypt volume, potential cost impact or security incident.
Investigation:
# 1. Query CloudTrail for decrypt events in the last hour
aws cloudtrail lookup-events \
--lookup-attributes AttributeKey=EventName,AttributeValue=Decrypt \
--start-time $(date -u -d '1 hour ago' +%Y-%m-%dT%H:%M:%SZ) \
--query 'Events[*].[EventTime,Username,Resources[0].ResourceName]' \
--output table
# 2. Identify unusual source IPs or principals
aws cloudtrail lookup-events \
--lookup-attributes AttributeKey=EventName,AttributeValue=Decrypt \
--start-time $(date -u -d '1 hour ago' +%Y-%m-%dT%H:%M:%SZ) \
--query 'Events[*].CloudTrailEvent' --output text | \
jq -r '.sourceIPAddress' | sort | uniq -c | sort -rn | head -10
Response actions:
- If malicious: Disable the compromised credentials immediately via IAM
- If misconfigured application: Fix the code (often a loop calling decrypt unnecessarily)
- Document the incident and update monitoring thresholds
Scenario 3: Key Pending Deletion
Symptoms: Key showing PendingDeletion state, 7-30 day countdown active.
Response:
# 1. Cancel deletion if the key is still needed
aws kms cancel-key-deletion --key-id <key-id>
# 2. Re-enable the key (canceling deletion leaves it disabled)
aws kms enable-key --key-id <key-id>
# 3. Investigate who scheduled deletion
aws cloudtrail lookup-events \
--lookup-attributes AttributeKey=EventName,AttributeValue=ScheduleKeyDeletion \
--query 'Events[*].[EventTime,Username]' --output table
🤔 Reflection:
How often are your KMS keys reviewed and rotated in your current environment? Is there a defined process, or does it happen ad-hoc? Consider adding key reviews to your quarterly security checklist.
Integrating KMS with AWS Services
KMS integrates seamlessly with dozens of AWS services. Here’s how the most common ones work:
S3 Server-Side Encryption (SSE-KMS)
When you enable SSE-KMS on a bucket, S3 calls GenerateDataKey for each object and stores the encrypted data key alongside the object. Decryption happens transparently when authorized users download.
EBS Volume Encryption
Encrypted EBS volumes use envelope encryption. AWS creates a grant allowing EC2 to decrypt the volume key on your behalf during instance launch.
RDS Encryption at Rest
RDS encrypts database storage, automated backups, snapshots, and read replicas. Once enabled, you can’t disable it—plan encryption from day one.
Lambda Environment Variables
Lambda encrypts environment variables using KMS. You can use the default service key or specify a CMK for additional control.
EKS Secrets Encryption
Kubernetes secrets in EKS can be encrypted using a CMK, adding a layer of protection beyond etcd encryption.
SSM Parameter Store SecureString
Parameters stored as SecureString are encrypted with KMS. Perfect for database passwords, API keys, and configuration secrets.
Secrets Manager
Every secret in Secrets Manager is encrypted with KMS. Automatic rotation? Secrets Manager handles the decryption and re-encryption automatically.
📚 Related: Terraform KMS Integration Guide | Secrets Manager Deep Dive
Example: Encrypting Terraform Remote State
resource "aws_kms_key" "terraform_state" {
description = "Terraform state encryption"
deletion_window_in_days = 30
enable_key_rotation = true
tags = {
Environment = "Production"
ManagedBy = "Terraform"
}
}
resource "aws_s3_bucket_server_side_encryption_configuration" "terraform" {
bucket = aws_s3_bucket.terraform_state.id
rule {
apply_server_side_encryption_by_default {
kms_master_key_id = aws_kms_key.terraform_state.arn
sse_algorithm = "aws:kms"
}
bucket_key_enabled = true # Important for cost optimization!
}
}
🤔 Reflection:
Would you choose a CMK or an AWS-managed key when building a multi-account CI/CD pipeline? Consider cross-account access requirements and operational complexity.
Advanced KMS Features
For complex architectures, these advanced features become essential.
Multi-Region Keys
Multi-region keys are replicas that share the same key material across AWS regions. A key encrypted in us-east-1 can be decrypted in eu-west-1 using the replica—without cross-region API calls.
Critical for disaster recovery and globally distributed applications.
Real-world scenario: Your primary application runs in us-east-1, encrypting database backups. When you fail over to eu-west-1, the replica key lets you decrypt those backups immediately without any key management changes.
Important: Each primary and replica multi-region key is billed separately. A key replicated to 3 regions costs $3/month plus individual API charges per region. See Multi-Region Keys documentation.
Asymmetric Keys
Asymmetric keys support RSA and ECC algorithms for:
- Digital signature verification (code signing, document signing)
- Encryption outside AWS (share public key externally)
- JWT token signing
Real-world scenario: Your CI/CD pipeline signs container images using a KMS asymmetric key. External systems can verify signatures using the public key without AWS access.
# Create an asymmetric signing key
aws kms create-key \
--key-spec RSA_2048 \
--key-usage SIGN_VERIFY \
--description "Container image signing key"
# Get the public key for external distribution
aws kms get-public-key --key-id <key-id> \
--query 'PublicKey' --output text | base64 --decode > public_key.der
HMAC Keys
HMAC keys generate hash-based message authentication codes for:
- API request authentication
- Token validation
- Data integrity verification
Real-world scenario: Your API gateway validates incoming webhook signatures using a KMS HMAC key, ensuring requests actually come from your payment provider.
# Generate HMAC for a webhook payload
aws kms generate-mac \
--key-id alias/webhook-hmac-key \
--mac-algorithm HMAC_SHA_256 \
--message fileb://webhook_payload.json \
--query 'Mac' --output text
External Key Stores (XKS)
For organizations requiring keys stored in their own HSMs outside AWS, External Key Store (XKS) allows KMS to communicate with external key managers. This satisfies strict compliance requirements (FIPS 140-2 Level 3, specific regulatory mandates) while maintaining KMS integration benefits.
XKS workflow:
- You deploy an XKS proxy in your environment
- The proxy connects to your external HSM
- KMS routes cryptographic operations through the proxy to your HSM
- Key material never enters AWS
This is complex to set up and operate—use it only when compliance absolutely requires it.
Key Material Import
You can import your own key material instead of letting AWS generate it. Useful for specific compliance scenarios, but adds operational complexity—you’re responsible for key material backup and durability.
Common KMS Mistakes to Avoid
I’ve seen these mistakes cause real production issues:
Using AWS-managed keys for sensitive production data limits your ability to control access, rotate on custom schedules, or share across accounts. Spend the dollar per month for CMKs.
Granting kms:* permissions is lazy and dangerous. Be specific about which actions each principal needs.
Not enabling key rotation leaves you with the same key material forever. Enable rotation on every symmetric CMK unless you have a documented reason not to.
Leaving keys in “pending deletion” limbo without review. Set up alerts for this state and investigate before the deletion window expires.
Storing encrypted data keys in application logs defeats the purpose of encryption. Audit your logging to ensure plaintext keys never appear.
Using one CMK for everything creates a blast radius problem. If that key is compromised or accidentally deleted, all your encrypted data is affected.
Allowing root account to perform encryption operations in production is unnecessary. Delegate to specific roles with minimal required permissions.
⚠️ Real Incident (Anonymized):
A client’s production database became inaccessible after an engineer accidentally disabled a KMS key during “cleanup.” Because they had no CloudTrail alerts configured, the issue wasn’t discovered for 6 hours—when the overnight batch job failed. The fix took 30 seconds (re-enable the key), but the incident cost them an SLA breach. Lesson: Always set upDisableKeyandScheduleKeyDeletionalerts.
KMS Pricing: What You’ll Actually Pay
KMS isn’t free, and costs can add up in high-volume environments.
Pricing as of 2025 (always verify at AWS KMS Pricing):
Key Storage
Customer-managed keys cost $1.00 per month per key (prorated hourly). AWS-managed keys have no storage charge.
API Request Costs
Symmetric key operations cost $0.03 per 10,000 requests. This includes Encrypt, Decrypt, GenerateDataKey, GenerateDataKeyWithoutPlaintext, and ReEncrypt.
Asymmetric operations (RSA, ECC) cost $0.10 – $0.15 per 10,000 requests depending on key type and operation.
Some calls are free: DescribeKey, GetKeyPolicy, ListKeys, ListAliases.
Multi-Region Key Costs
Each replica is billed as a separate key ($1/month each), and API calls are billed in each region where they occur.
Real-World Cost Example
A Lambda function that decrypts configuration on every cold start:
- 1 million invocations/month
- 100,000 cold starts (assume 10% cold start rate)
- 100,000 decrypt calls × $0.03/10,000 = $0.30/month
A high-throughput application decrypting every request:
- 100 million requests/month
- 100 million decrypt calls × $0.03/10,000 = $300/month
S3 Bucket Keys: Dramatic Cost Savings
Without bucket keys, S3 makes a KMS API call for every object operation. With bucket keys enabled, S3 uses a bucket-level data key and significantly reduces KMS calls.
Real comparison:
- Without bucket keys: 10 million S3 PUTs/month = 10 million GenerateDataKey calls = $30/month
- With bucket keys: Same 10 million PUTs ≈ 1,000 GenerateDataKey calls = $0.003/month
That’s a 99.99% cost reduction for high-volume S3 workloads. Always enable bucket keys for SSE-KMS buckets.
# Enable bucket keys on existing bucket
aws s3api put-bucket-encryption \
--bucket my-bucket \
--server-side-encryption-configuration '{
"Rules": [{
"ApplyServerSideEncryptionByDefault": {
"SSEAlgorithm": "aws:kms",
"KMSMasterKeyID": "alias/my-key"
},
"BucketKeyEnabled": true
}]
}'
FAQ: AWS KMS Common Questions
What is AWS KMS?
AWS Key Management Service is a managed service for creating, controlling, and managing cryptographic keys used to encrypt data across AWS services and applications.
What are CMKs in AWS KMS?
Customer-managed keys (CMKs) are KMS keys that you create, own, and manage. They give you full control over key policies, rotation, and cross-account sharing.
What is envelope encryption in AWS?
Envelope encryption is a technique where a master key encrypts a data key, and the data key encrypts your actual data. This provides both security and performance benefits.
How does AWS KMS pricing work?
KMS charges $1/month per customer-managed key plus $0.03 per 10,000 API requests for symmetric operations. AWS-managed keys have no storage fee but still incur request charges.
Is KMS required for S3/RDS/Lambda encryption?
While these services can use their own managed encryption, KMS provides superior control, audit capabilities, and cross-service key management.
How do key policies and IAM policies differ in KMS?
Key policies are attached directly to keys and control access at the resource level. IAM policies grant permissions to identities. Both must allow access for operations to succeed (unless the key policy explicitly grants direct access).
Conclusion
KMS isn’t just another AWS service to check off your security list. It’s the backbone of encryption across your entire cloud infrastructure.
When you understand envelope encryption, master key policies, and implement proper access controls, you’ve solved one of the hardest problems in cloud security—managing cryptographic keys at scale without operational nightmare.
The patterns we covered—CMKs for control, envelope encryption for performance, CloudTrail for visibility—these are the foundations of production-ready encryption.
Start with your most sensitive workloads. Enable CMKs, configure proper policies, turn on rotation, and set up monitoring. Then expand from there.
Secure your keys, and you secure your entire cloud.
👉 Ready to go deeper? Take the free AWS KMS Crash Course—Learn encryption, key policies, rotation, and envelope encryption through hands-on labs and real-world scenarios.
About the Author
Srikanth Ch is a Senior DevOps Engineer with extensive experience in AWS architecture, Kubernetes, and CI/CD pipelines. He holds AWS Solutions Architect and Security Specialty certifications and has helped organizations implement enterprise-grade encryption strategies across multi-account AWS environments. Srikanth writes at thedevopstooling.com to share practical DevOps knowledge with the community.
References
- AWS KMS Developer Guide
- AWS KMS Pricing
- KMS Encrypt API Documentation
- GenerateDataKeyWithoutPlaintext API
- Multi-Region Keys Overview
- KMS Key Rotation
- KMS CloudTrail Logging
[thedevopstooling.com — Your path to DevOps mastery]
