AWS KMS Fundamentals

KMS (Key Management Service) is AWS’s managed cryptographic service. It stores keys, performs cryptographic operations, and — most importantly — serves as the trust root almost every other AWS service uses for encryption at rest. Understanding KMS is prerequisite to understanding S3, EBS, RDS, Secrets Manager, and any serious compliance posture.

What KMS does

Three distinct things:

  1. Stores cryptographic keys in FIPS 140-2 validated HSMs, never exported
  2. Performs operations with those keys (encrypt, decrypt, sign, verify, generate data key)
  3. Integrates with other AWS services so they can encrypt customer data without custom crypto code

The keys themselves never leave KMS. Applications call the KMS API; KMS does the work.

KMS Keys (the canonical name)

What used to be called “CMKs” (Customer Master Keys) are now called KMS keys. Three types by ownership:

TypeWho owns / managesTypical use
AWS-ownedAWS fully manages; invisible to youDefault encryption on services where you don’t configure a key (e.g. DynamoDB default)
AWS-managedAWS creates in your account, you can see them but not modifyDefault for service-specific keys (aws/s3, aws/ebs, etc.)
Customer-managed (CMK)You create; you control policy, rotation, deletionProduction workloads, compliance, cross-account sharing

Practical rule: use customer-managed keys for anything you care about. AWS-managed keys are fine for quick starts but offer less control (can’t grant cross-account access, can’t set custom rotation, limited visibility in CloudTrail).

Key spec / origin / usage

  • Key spec — symmetric (SYMMETRIC_DEFAULT, AES-256-GCM) or asymmetric (RSA, ECC, SM2)
  • Key usageENCRYPT_DECRYPT or SIGN_VERIFY (asymmetric can also be KEY_AGREEMENT)
  • OriginAWS_KMS (KMS generates), EXTERNAL (BYOK — you import material), AWS_CLOUDHSM (backed by CloudHSM cluster), EXTERNAL_KEY_STORE (XKS — external HSM)

Most real-world use: symmetric, AWS-origin, customer-managed.

Envelope encryption — the pattern to internalise

KMS does not encrypt arbitrary large payloads directly (4 KB limit per call). Instead, every service uses envelope encryption:

     1. App/service calls GenerateDataKey(KMS key)
            ↓
     2. KMS returns two things:
           - plaintext data key (AES-256 key)
           - encrypted data key (the key, wrapped by the KMS key)
            ↓
     3. App encrypts the payload with the plaintext data key (AES-GCM)
            ↓
     4. App stores: ciphertext + encrypted data key
            ↓
     5. App forgets the plaintext data key

To decrypt later:

  1. Call Decrypt(encrypted data key) → KMS returns plaintext data key
  2. Use the plaintext data key to decrypt the payload
  3. Forget the plaintext data key again

Benefits:

  • The large payload never leaves your infra
  • The KMS call is small and cheap
  • KMS key never touches plaintext data
  • Auditable: every key-unwrap is logged in CloudTrail

Every SSE-KMS-encrypted S3 object, EBS volume, RDS volume, Secrets Manager secret, etc. is using this pattern under the hood.

Key policies — who can use the key

Unlike most AWS resources, KMS keys use resource-based policy (the key policy) as the primary access control, not IAM alone. The key policy is required — a key without one is unusable, including by its creator.

The typical pattern:

{
  "Statement": [
    {
      "Sid": "EnableRootAccess",
      "Effect": "Allow",
      "Principal": { "AWS": "arn:aws:iam::<account>:root" },
      "Action": "kms:*",
      "Resource": "*"
    },
    {
      "Sid": "KeyAdmins",
      "Effect": "Allow",
      "Principal": { "AWS": "arn:aws:iam::<account>:role/KeyAdmin" },
      "Action": ["kms:Create*", "kms:Describe*", "kms:Put*", "kms:Tag*", ...],
      "Resource": "*"
    },
    {
      "Sid": "KeyUsers",
      "Effect": "Allow",
      "Principal": { "AWS": "arn:aws:iam::<account>:role/AppRole" },
      "Action": ["kms:Encrypt","kms:Decrypt","kms:GenerateDataKey*","kms:DescribeKey"],
      "Resource": "*"
    }
  ]
}

Key insight: the root statement delegates access control to IAM for that account. Without it, only the principals explicitly named in the key policy can use the key — dangerous if you lose your admin principal.

Cross-account access

Allow the other account’s principal in the key policy, and that other account must grant its own principals IAM permission to use the key. Both sides must say yes.

Grants — scoped, temporary delegation

A grant is a narrow, time-limited permission attached to a key. Typically used by AWS services that need temporary access on your behalf (e.g. RDS creating a snapshot). Grants can be revoked independently without editing the key policy. Prefer grants over broad key-policy additions for service integrations.

Rotation

Automatic rotation (customer-managed symmetric keys)

  • Enable automatic annual rotation → KMS rotates the backing key material yearly
  • The key ID stays the same; old key material is retained so existing ciphertext still decrypts
  • Transparent to apps
  • Recommended for any long-lived customer-managed key

Manual rotation

  • Create a new key, update aliases/configuration to point at the new key
  • Re-encrypt old data (optionally) via ReEncrypt — decrypts with old key, encrypts with new, all inside KMS (plaintext never emitted)

Asymmetric keys

  • Automatic rotation not supported on asymmetric keys (would break signature verification)
  • Must roll keys manually

Aliases

An alias is a friendly name → key-ID pointer. alias/prod-data. Rotate the underlying key by repointing the alias. Referenced in code/config instead of raw key IDs.

Services reference aliases (aws/s3, alias/my-key). You reference aliases. One key can have many aliases; each alias points to exactly one key.

Cost model

  • **0.0014)
  • Usage: $0.03 per 10,000 requests — cheap for normal use, can add up in high-QPS decrypt paths
  • AWS-managed keys — free
  • Symmetric vs asymmetric — asymmetric costs more per request and per key

At high request rates (data-key wrap/unwrap in tight loops), consider data-key caching — reuse a plaintext data key for multiple encrypt operations over a short window. The AWS Encryption SDK supports this.

Deletion — the scheduled-delete safety

KMS keys cannot be deleted immediately. You schedule deletion with a 7-30 day waiting period (30 default). During that window the key is “pending deletion” but can still decrypt — you cancel deletion if you need to restore. After the window, the key material is destroyed and all data encrypted with it is cryptographically unrecoverable.

Audit before scheduling deletion. Once the window passes, recovery is impossible.

Multi-Region keys

A multi-Region key is a key with the same key material replicated across regions. Ciphertext encrypted in region A can be decrypted in region B by the replica key — useful for multi-region DR, global applications. Policy, aliases, rotation are managed independently per region.

Don’t confuse with KMS grants across regions (different thing).

What “encryption at rest” actually means on AWS

When you tick “encrypt this volume” / “SSE-KMS this bucket”:

  1. Service calls GenerateDataKey against your CMK
  2. Service gets (plaintextDataKey, encryptedDataKey)
  3. Service encrypts your data with plaintextDataKey
  4. Service stores encryptedDataKey alongside the ciphertext
  5. On read, service calls Decrypt(encryptedDataKey) to get back plaintextDataKey
  6. Service decrypts the data

Your bill for KMS here is one Decrypt per S3 object / one per EBS volume mount / etc.

Integration highlights

ServiceKMS usage
S3SSE-KMS for server-side encryption; bucket keys reduce Decrypt calls
EBSVolume encryption; snapshots inherit; one decrypt at attach time
RDS / AuroraStorage + snapshot + backup encryption
Secrets Manager / Parameter StoreSecrets encrypted under a KMS key; choose which key
SSM Session Manager logsKMS-encrypted
SNS/SQSMessage-level encryption via KMS
DynamoDBTables encrypted; choose AWS-owned / AWS-managed / customer-managed

Common pitfalls

  1. Deleting the “root” statement from a key policy → locks you out. Don’t edit the default statement without a thorough review.
  2. Cross-account access requires both sides. Key policy on the owner side, IAM policy on the caller side.
  3. Using AWS-managed keys when you need cross-account — AWS-managed keys can’t be shared. Use customer-managed.
  4. Forgetting kms:*-level actions in policieskms:GenerateDataKey* (note the wildcard) is often required alongside kms:Decrypt.
  5. Throttling at high QPS — KMS has per-region quotas. Enable S3 Bucket Keys to dramatically reduce calls for high-throughput S3 + SSE-KMS workloads.
  6. Asymmetric key confusion — asymmetric keys split into ENCRYPT_DECRYPT, SIGN_VERIFY, KEY_AGREEMENT. Can’t mix.
  7. ViaService condition — powerful IAM/Key-policy condition restricting a key’s use to a specific AWS service. Commonly missing in tighter policies.

Mental model

  • KMS = trust root. It’s the place where key material lives and where unwrap decisions happen. Everything else is a data plane.
  • Envelope encryption = the pattern. Data keys (cheap, fast, disposable) encrypt data; KMS keys (expensive, audited, long-lived) wrap data keys.
  • Key policy = the gate. IAM alone isn’t enough; the key policy must also admit you.
  • Aliases = indirection. Point them at new keys to rotate without app changes.
  • Grants = the service delegation primitive.

See also