AWS KMS

By Vishvesh Patel · DevOps EngineerUpdated September 16, 2026

Encryption keys that actually work. Key policies versus IAM, AWS managed vs customer managed keys, envelope encryption, grants and rotation.

KMS creates and controls encryption keys. The keys themselves never leave the service. You send data or a data key to KMS and it returns the encrypted or decrypted result, so the key material is never in your application's memory or your logs.

AWS Security Services Overview

KMS
key management
Secrets Manager
rotate secrets
IAM
identity & access
↓ protect
S3
RDS
EC2
Lambda
WAF
block bad requests
CloudTrail
audit all API calls
Config
compliance rules

Almost everything that goes wrong with KMS comes down to one thing that works differently from the rest of AWS: a key policy is required, and IAM alone is not enough.

The key policy rule

For most AWS resources, an IAM policy granting access is sufficient. For KMS it is not. Every key has a key policy, and access requires permission from both the key policy and IAM, unless the key policy explicitly delegates to IAM.

That delegation is what the default key policy does:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "EnableIAMUserPermissions",
      "Effect": "Allow",
      "Principal": { "AWS": "arn:aws:iam::123456789012:root" },
      "Action": "kms:*",
      "Resource": "*"
    }
  ]
}

Principal: root here does not mean the root user. It means "this account", and it allows IAM policies in the account to grant access to this key. Remove that statement and IAM becomes irrelevant, only principals named directly in the key policy can use the key, and an administrator with AdministratorAccess cannot.

That is also how a key gets orphaned. A key policy naming only a deleted role locks the key permanently, and recovery requires AWS Support.

A more careful policy separates administration from use:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "Administration",
      "Effect": "Allow",
      "Principal": { "AWS": "arn:aws:iam::123456789012:role/KeyAdmin" },
      "Action": ["kms:Create*", "kms:Describe*", "kms:Enable*", "kms:List*",
                 "kms:Put*", "kms:Update*", "kms:Revoke*", "kms:Disable*",
                 "kms:Get*", "kms:Delete*", "kms:ScheduleKeyDeletion",
                 "kms:CancelKeyDeletion"],
      "Resource": "*"
    },
    {
      "Sid": "Use",
      "Effect": "Allow",
      "Principal": { "AWS": "arn:aws:iam::123456789012:role/AppRole" },
      "Action": ["kms:Encrypt", "kms:Decrypt", "kms:ReEncrypt*",
                 "kms:GenerateDataKey*", "kms:DescribeKey"],
      "Resource": "*",
      "Condition": {
        "StringEquals": { "kms:ViaService": "s3.eu-west-1.amazonaws.com" }
      }
    }
  ]
}

Note the separation: an administrator can manage the key and cannot decrypt with it. kms:ViaService restricts use to requests arriving through a named service, so the role can decrypt S3 objects and cannot call kms:Decrypt directly on arbitrary ciphertext.

Which key type

AWS managedCustomer managed
Created byAWS, automaticallyYou
CostFree~$1/month per key
Policy controlNoneFull
RotationYearly, automaticConfigurable
Cross-accountNoYes
DeletableNoYes

AWS managed keys, aws/s3, aws/rds, appear automatically and cost nothing, which makes them the right default for encryption-at-rest where you have no specific access requirement.

Create a customer managed key when you need to control who can decrypt independently of who can read the resource, share access with another account, prove key control for compliance, or audit key usage separately.

aws kms create-key --description "orders app" \
  --key-usage ENCRYPT_DECRYPT --key-spec SYMMETRIC_DEFAULT \
  --policy file://key-policy.json

aws kms create-alias --alias-name alias/orders --target-key-id "$KEY_ID"

Always create an alias. Key IDs are opaque UUIDs, and referencing alias/orders throughout your infrastructure means the underlying key can be rotated or replaced without changing anything else.

Envelope encryption

KMS will not encrypt more than 4 KB directly. That limit is deliberate: it pushes you toward envelope encryption, which is how every AWS service does it.

aws kms generate-data-key --key-id alias/orders --key-spec AES_256
{
  "CiphertextBlob": "AQIDAHh...",
  "Plaintext": "VGhpcyBpcyBhIGtleQ==",
  "KeyId": "arn:aws:kms:eu-west-1:123456789012:key/abc-123"
}

You get the same key twice: plaintext to encrypt your data locally, and encrypted to store alongside it. Use the plaintext, discard it from memory, and keep the ciphertext. To decrypt later, send the ciphertext to kms:Decrypt to recover the plaintext key.

The point is that bulk encryption happens locally at full speed, and KMS is only involved for the small key, so you are not limited by KMS throughput or charged per megabyte.

Use the AWS Encryption SDK rather than implementing this yourself. It handles the format, the algorithm choices and data key caching, which matters: a high-volume application calling generate-data-key per object hits KMS request throttling and accumulates real cost.

Encryption context

An underused feature that prevents a whole class of mistake:

aws kms encrypt --key-id alias/orders \
  --plaintext fileb://data.bin \
  --encryption-context 'tenant=acme,purpose=orders'

The context is additional authenticated data, not secret, but cryptographically bound to the ciphertext. Decryption must supply exactly the same context or it fails, so ciphertext encrypted for one tenant cannot be decrypted in another tenant's context even by a caller with full key access.

It also appears in CloudTrail, which makes key usage auditable in terms of what was accessed rather than just how often.

Rotation

aws kms enable-key-rotation --key-id alias/orders --rotation-period-in-days 365

Automatic rotation generates new key material annually while keeping all previous material, so old ciphertext still decrypts with no re-encryption. The key ID and ARN do not change, so nothing in your infrastructure needs updating.

This is genuinely free of operational cost, and there is no reason not to enable it on every customer managed key. Note it does not apply to imported key material or asymmetric keys. Those need manual rotation with a new key and an alias update.

Deletion has a waiting period

aws kms schedule-key-deletion --key-id "$KEY_ID" --pending-window-in-days 30
aws kms cancel-key-deletion --key-id "$KEY_ID"

Minimum seven days, maximum thirty. The delay exists because deleting a key destroys every piece of data encrypted with it, permanently. There is no recovery, no support escalation, nothing.

Before scheduling deletion, confirm nothing uses the key:

aws cloudtrail lookup-events \
  --lookup-attributes AttributeKey=ResourceName,AttributeValue="$KEY_ARN" \
  --start-time 2026-08-16 --query 'Events[].EventName' --output text | sort -u

Disabling the key first is the safer approach. It produces immediate, visible failures rather than a silent countdown, and it is reversible.

Cross-account access

Two sides, and missing either produces the same AccessDenied:

On the key policy, allow the other account:

{
  "Effect": "Allow",
  "Principal": { "AWS": "arn:aws:iam::210987654321:root" },
  "Action": ["kms:Decrypt", "kms:DescribeKey"],
  "Resource": "*"
}

In the other account, an IAM policy must also grant those actions on the key ARN. The key policy permits it; IAM in the consuming account still has to allow it.

For temporary or programmatic delegation, grants are better than editing the policy:

aws kms create-grant --key-id alias/orders \
  --grantee-principal arn:aws:iam::123456789012:role/BatchJob \
  --operations Decrypt DescribeKey \
  --retiring-principal arn:aws:iam::123456789012:role/Admin

Grants can be revoked individually without touching the key policy, which makes them safer for automation.

Common errors

An error occurred (AccessDeniedException) when calling the Decrypt operation:
The ciphertext refers to a customer master key that does not exist, does not
exist in this region, or you are not allowed to access.

Deliberately vague, so it does not reveal whether the key exists. Check, in order: the key policy allows your principal, your IAM policy allows the action, you are in the right region, and any kms:ViaService or encryption context condition is satisfied.

InvalidCiphertextException

Usually a missing or mismatched encryption context, or ciphertext from a different key.

KMSInvalidStateException: ... is pending deletion

Cancel the deletion. The key is unusable until you do.

ThrottlingException: Rate exceeded

Too many KMS requests. Use data key caching rather than requesting a key per operation.

Quick reference

TaskCommand
Create a keyaws kms create-key --policy file://policy.json
Alias itaws kms create-alias --alias-name alias/<n>
Envelope keyaws kms generate-data-key --key-spec AES_256
Encrypt (≤4 KB)aws kms encrypt --key-id alias/<n> --plaintext fileb://f
Decryptaws kms decrypt --ciphertext-blob fileb://c
Enable rotationaws kms enable-key-rotation --key-id alias/<n>
Read the policyaws kms get-key-policy --policy-name default
Grant temporarilyaws kms create-grant --operations Decrypt
Schedule deletionaws kms schedule-key-deletion --pending-window-in-days 30

Key takeaways

  • Access needs both the key policy and IAM. IAM alone is never enough.
  • Principal: root in a key policy means "this account", not the root user.
  • A key policy naming only a deleted role orphans the key: recovery needs AWS Support.
  • AWS managed keys are free and fine unless you need policy control or cross-account access.
  • Always use an alias, never a bare key ID.
  • KMS encrypts 4 KB. Everything larger uses envelope encryption. Use the Encryption SDK.
  • Encryption context is bound to the ciphertext and prevents cross-tenant decryption.
  • Enable rotation. It is free and requires no re-encryption.
  • Deleting a key destroys the data permanently. Disable first.

Check your understanding

Exercise

An administrator who cannot decrypt

A role with AdministratorAccess gets AccessDeniedException calling kms:Decrypt on a customer managed key. The key exists in the same region and account. What is the most likely cause?

Exercise

Throttled by KMS at high volume

A service encrypting thousands of small records per second calls kms:GenerateDataKey for each one and starts hitting ThrottlingException. What is the correct fix?


Frequently Asked Questions

Why do I get AccessDenied on KMS when my IAM policy allows it?

Because KMS requires permission from both the key policy and IAM. Unlike most AWS resources, an IAM policy granting kms:Decrypt does nothing unless the key policy also permits that principal, usually via the default statement allowing the account root, which delegates authority to IAM. Check the key policy first with get-key-policy, then your IAM policy, then whether you are in the right region, and finally any kms:ViaService or encryption context conditions attached to the grant.

What is the difference between AWS managed and customer managed keys?

AWS managed keys such as aws/s3 are created automatically the first time a service needs one, cost nothing, rotate yearly and cannot be modified, shared across accounts or deleted. Customer managed keys cost about a dollar a month each and give you a key policy you control, configurable rotation, cross-account sharing, grants and independent audit. Use AWS managed keys for straightforward encryption at rest; create a customer managed key when access control, cross-account sharing or compliance evidence matters.

What is envelope encryption and why does KMS use it?

KMS refuses to encrypt more than 4 KB directly, which forces the pattern: ask for a data key, receive it in both plaintext and encrypted form, encrypt your data locally with the plaintext copy, discard that copy from memory, and store the encrypted key alongside the ciphertext. To decrypt, send the encrypted key back to KMS. Bulk encryption then runs locally at full speed with KMS only handling a small key, so throughput is not limited and you are not billed per megabyte.

Should I enable automatic key rotation?

Yes, on every customer managed key. Rotation generates fresh key material annually while retaining all previous material, so existing ciphertext continues to decrypt with no re-encryption and the key ID, ARN and alias are unchanged. Nothing in your infrastructure needs to know it happened. There is no meaningful cost or operational burden. It does not cover imported key material or asymmetric keys, which require creating a new key and repointing the alias manually.

What happens if I delete a KMS key?

Every piece of data encrypted with it becomes permanently unrecoverable. There is no restore, no support escalation and no backup of the key material. That is why deletion is scheduled with a mandatory waiting period of seven to thirty days rather than executed immediately, and why cancel-key-deletion exists. Disable the key first instead: it produces immediate, visible failures from anything still using it, and is instantly reversible, which is a far better way to discover an unknown dependency than a silent countdown.

How do I give another AWS account access to my KMS key?

Both sides must allow it. Add a statement to the key policy naming the other account as principal and listing the permitted actions, and have an IAM policy in that account grant the same actions on the key's ARN. Missing either produces an identical access denied error, which makes it slow to diagnose. For temporary or programmatic access, a grant is preferable to editing the key policy. It can be created and revoked independently without risking a policy change that locks people out.

Continue Learning

Explore Related Topics

When it breaks

Related Resources