Skip to content
ExponaBLOG
AWS FOR DEVELOPERS · DAY 058 / 100

Encryption with KMS: what every AWS developer should know

Managed keys for data at rest.

Expona Admin7 min read

Introduction

When you store data in AWS, encryption is not optional—it's foundational. Yet many teams treat AWS Key Management Service (KMS) as an afterthought, defaulting to AWS-managed keys without understanding the trade-offs or missing out on the visibility and control that customer-managed keys provide. Encryption with KMS: what every AWS developer should know starts with a simple fact: every encryption key decision you make today affects your compliance posture, audit trail, and ability to respond to incidents months or years later.

By the end of this article, you'll understand when to use AWS-managed versus customer-managed keys, how key policies work, which AWS services integrate with KMS, and why CloudTrail auditing matters for every key operation.

AWS-Managed vs Customer-Managed Keys

KMS offers two types of keys, and the distinction is crucial. AWS-managed keys are created and maintained by AWS on your behalf. They're free, automatic, and transparent—S3 encrypts objects with them by default, RDS encrypts databases, and your EBS volumes get encrypted without you lifting a finger. The trade-off is control: AWS rotates these keys annually, and you cannot customize their rotation schedule, policies, or audit retention.

Customer-managed keys, by contrast, are keys you create and own. You control their lifecycle, rotation policies, and who can use them. You pay a small monthly fee per key plus per-API-call charges, but you gain what matters most in production: visibility and agency.

Here's a practical scenario: you're building a SaaS platform handling payment data. Compliance requires you to rotate encryption keys every 90 days and prove it in an audit. AWS-managed keys won't let you enforce that. A customer-managed key gives you explicit control:

bash
aws kms create-key \
  --description "SaaS payment data encryption" \
  --key-usage ENCRYPT_DECRYPT \
  --region us-east-1

This returns a key ID (like arn:aws:kms:us-east-1:123456789012:key/12345678-1234-1234-1234-123456789012). Now you own it. You can rotate it:

bash
aws kms enable-key-rotation --key-id <your-key-id>

AWS will now rotate the key annually—but you can also rotate it on demand if needed. More importantly, you control the key policy (covered next), so you decide which IAM roles, services, and even external accounts can use it.

Choose AWS-managed keys for non-sensitive workloads where compliance is loose. Choose customer-managed keys for sensitive data, multi-team environments, and any system where you need to prove who accessed the encryption key and when.

Key Policies: Who Can Do What

A key policy is a resource-based policy (similar to an S3 bucket policy) that controls access to a KMS key. It's different from IAM policies because it lives on the key itself and takes effect immediately, without relying on IAM evaluation alone.

When you create a customer-managed key, you must define a key policy. If you don't specify one, AWS creates a default that gives the key creator (your AWS account root) full permissions. Here's a realistic policy for a multi-team setup:

bash
{
  "Sid": "Enable IAM policies",
  "Effect": "Allow",
  "Principal": {
    "AWS": "arn:aws:iam::123456789012:root"
  },
  "Action": "kms:*",
  "Resource": "*"
}

{
  "Sid": "Allow payment service to encrypt and decrypt",
  "Effect": "Allow",
  "Principal": {
    "AWS": "arn:aws:iam::123456789012:role/PaymentServiceRole"
  },
  "Action": [
    "kms:Decrypt",
    "kms:GenerateDataKey",
    "kms:DescribeKey"
  ],
  "Resource": "*"
}

{
  "Sid": "Allow security team to audit",
  "Effect": "Allow",
  "Principal": {
    "AWS": "arn:aws:iam::123456789012:role/SecurityAuditRole"
  },
  "Action": [
    "kms:DescribeKey",
    "kms:GetKeyPolicy",
    "kms:ListKeys"
  ],
  "Resource": "*"
}

Notice the specificity: the payment service only gets Decrypt and GenerateDataKey—not CreateGrant or ScheduleKeyDeletion. The security team can describe and audit but not decrypt. This principle of least privilege is how you prevent a compromised service from accidentally deleting or exfiltrating your encryption keys.

A common mistake is making the policy too permissive early on ("we'll tighten it later") and forgetting. By the time you audit, multiple services have broad access, and rolling it back is risky. Start restrictive and add permissions only when needed.

You can also grant temporary access using KMS Grants, which is useful for short-lived operations or cross-account access. A Lambda function processing encrypted S3 objects might need temporary decrypt permissions that auto-revoke after the function runs:

bash
aws kms create-grant \
  --key-id <your-key-id> \
  --grantee-principal arn:aws:iam::123456789012:role/LambdaExecutionRole \
  --operations Decrypt GenerateDataKey \
  --retiring-principal arn:aws:iam::123456789012:role/LambdaExecutionRole

The retiring principal can revoke the grant when done, keeping access minimal.

Service Integration: KMS Across AWS

The real power of KMS emerges when you integrate it with other services. Instead of managing encryption manually in application code, AWS services use KMS transparently.

S3 + KMS: By default, S3 uses AES-256 encryption (SSE-S3). To use a customer-managed KMS key instead, specify it at bucket creation or per-object:

bash
aws s3api create-bucket \
  --bucket my-secure-bucket \
  --region us-east-1

aws s3api put-bucket-encryption \
  --bucket my-secure-bucket \
  --server-side-encryption-configuration '{
    "Rules": [{
      "ApplyServerSideEncryptionByDefault": {
        "SSEAlgorithm": "aws:kms",
        "KMSMasterKeyID": "arn:aws:kms:us-east-1:123456789012:key/12345678..."
      },
      "BucketKeyEnabled": true
    }]
  }'

Every object in this bucket is now encrypted with your key. If a data breach occurs and you need to invalidate that key, you can—and S3 won't decrypt those objects with a new key until you explicitly re-encrypt them.

RDS + KMS: When you create an RDS instance, you can specify a customer-managed KMS key. The database encrypts all storage at rest:

bash
aws rds create-db-instance \
  --db-instance-identifier my-postgres-db \
  --db-instance-class db.t3.micro \
  --engine postgres \
  --allocated-storage 20 \
  --kms-key-id arn:aws:kms:us-east-1:123456789012:key/12345678... \
  --storage-encrypted

RDS handles encryption and key rotation internally. You focus on policy and auditing.

EBS + KMS: EBS volumes encrypted with a customer-managed key ensure that snapshots and backups respect your key policies. If you replicate a snapshot to another region, you can use a different KMS key there.

Secrets Manager + KMS: When you store secrets (database passwords, API keys, certificate data), Secrets Manager encrypts them with KMS by default. Using a customer-managed key gives you fine-grained control over who can retrieve secrets:

bash
aws secretsmanager create-secret \
  --name prod/db-password \
  --secret-string '{"username":"admin","password":"..."}' \
  --kms-key-id arn:aws:kms:us-east-1:123456789012:key/12345678...

If a service is compromised, you can immediately revoke its KMS key policy permissions, and subsequent GetSecretValue calls will fail—even if the service still has an IAM permission to access Secrets Manager.

Audit: CloudTrail and Key Usage Logging

Every encryption and decryption operation in AWS generates a CloudTrail event. For customer-managed keys, these events are gold for compliance and incident response. When you decrypt a database snapshot to move data to a different account, CloudTrail logs it. When a misconfigured Lambda role tries to decrypt an object, CloudTrail shows the denial.

Enable CloudTrail logging for your entire AWS account (you should be doing this anyway):

bash
aws cloudtrail create-trail \
  --name prod-kms-audit \
  --s3-bucket-name my-cloudtrail-bucket \
  --region us-east-1

aws cloudtrail start-logging --trail-name prod-kms-audit

Now query for KMS events:

bash
aws cloudtrail lookup-events \
  --lookup-attributes AttributeKey=ResourceName,AttributeValue=<your-key-id> \
  --max-results 50

You'll see a JSON response with timestamps, IAM principals, and actions. For compliance audits requiring proof that only authorized services decrypt sensitive data, this is essential.

Beyond CloudTrail, enable KMS key logging (CloudWatch Logs) to track API calls specific to the key:

bash
aws kms put-key-policy \
  --key-id <your-key-id> \
  --policy-name default \
  --policy file://key-policy.json

Then ingest those logs into a SIEM or aggregation tool for real-time alerting on suspicious activity—like an unauthorized role attempting to decrypt.

Common Mistakes

  • Using AWS-managed keys for regulated data: Compliance frameworks often require you to control and rotate encryption keys. AWS-managed keys don't give you that control, and you can't prove rotation on your schedule.
  • Overly permissive key policies: Granting wildcard permissions ("*" on all actions) defeats the purpose of customer-managed keys. Start restrictive and expand only when necessary.
  • Forgetting the decryption path: A service can encrypt data with a KMS key, but if its IAM role doesn't have kms:Decrypt in the key policy, decryption fails silently. Test both directions early.
  • Not rotating keys regularly: Even with automatic rotation enabled, document your rotation schedule and review it annually. If you disable rotation without a reason, you've lost compliance visibility.
  • Ignoring cross-account access: If you replicate S3 buckets or RDS snapshots to another AWS account, that account needs permissions on your KMS key. This often breaks silently; use CloudTrail to debug.
  • Deleting keys without backup: KMS has a 7-30 day key deletion window where the key is still in use but scheduled for deletion. If a service tries to decrypt during that window, it fails. Always test failover before deleting.

Key Takeaways

  • Use customer-managed KMS keys for sensitive data, regulated workloads, and multi-team environments where control and auditability matter.
  • Key policies define access at the key level; write them restrictively, granting only the minimum actions required (Decrypt, GenerateDataKey, etc.).
  • Integrate KMS with S3, RDS, EBS, and Secrets Manager to encrypt data at rest without manual key management in application code.
  • Enable CloudTrail for your entire account and query KMS events to audit who decrypted what and when—essential for compliance investigations.
  • Rotate keys regularly (annually for customer-managed keys) and test failover scenarios before enforcing rotation policies in production.
  • Start with least-privilege policies and add permissions on-demand; it's easier to grant access later than to revoke it from a large number of services.
ShareXLinkedInReddit

Found this useful? Support the writing.

Buy me a coffee
Written by
Expona Admin
Site administrator

Comments

Loading comments…

Leave a comment

Comments are reviewed before they appear. Be kind and stay on topic.

Related articles