Filter concepts by levelShowing all levels.

AWS · Section 32

KMS and Encryption

Level
intermediate
Read
22 min
Concepts
3

KMS protects data through envelope encryption — a data key encrypts your data, and a KMS key encrypts only that small data key, so KMS never touches bulk data directly. Every key carries exactly one key policy; grants layer narrow, temporary permissions on top without editing it, and encryption context authenticates a call as additional (non-secret) data. Encryption at rest (server-side encryption, usually KMS-backed) and encryption in transit (TLS) are independent controls that protect different moments in a request's lifecycle. Authorization on a key always checks two layers together: the key policy, which is required and must explicitly enable IAM, and IAM policies, which then govern individual principals — an IAM Allow alone is never sufficient, though an IAM Deny always applies.

What is true here

  1. Envelope encryption: a data key encrypts your data; a KMS key encrypts only that data key, keeping KMS out of the bulk-data path.
  2. Every KMS key has exactly one key policy; grants add temporary, narrow access without editing it, and are commonly created/retired automatically by AWS services.
  3. Encryption context authenticates an encrypt/decrypt call as additional authenticated data — it is never secret and appears in CloudTrail.
  4. Encryption at rest (server-side encryption) and encryption in transit (TLS) are independent controls — enabling one never implies the other is also in effect.
  5. A KMS key policy must explicitly enable IAM policies before any IAM Allow can grant access to that key; an IAM Deny applies regardless of that enablement.

What you will be able to do

  • Explain why a KMS key encrypts a data key rather than the data itself, and what that buys operationally
  • Choose between editing a key policy and creating a grant for a given access need
  • Use encryption context correctly, without mistaking it for a secret
  • Verify both encryption at rest and encryption in transit independently, rather than assuming one implies the other
  • Debug a KMS AccessDenied by checking the key policy and IAM policy as two separate, both-required layers
From envelope encryption to the two-layer authorization model
protectsdataaccess to the key thatprotects it is governed by

Envelope encryption, key policy, grants, rotation

At rest vs. in transit

IAM + key policy, both required

  • Envelope encryption, key policy, grants, rotation
    • leads to At rest vs. in transit (protects data)
  • At rest vs. in transit
    • leads to IAM + key policy, both required (access to the key that protects it is governed by)
  • IAM + key policy, both required

KMS and Encryption

Envelope encryption and the KMS key-management vocabulary, encryption at rest vs. in transit, and why KMS authorization always evaluates both the key policy and IAM together.

Envelope Encryption, KMS Keys, Policies, Grants, and Rotation

coreintermediate

Envelope encryption uses two keys: a data key that encrypts your actual data, and a KMS key that encrypts (wraps) that data key. KMS never touches your bulk data directly — it only ever handles the small, wrapped data key. A key policy is the resource policy every KMS key must have, controlling who can use it. A grant hands out a narrow, temporary permission on a key without editing that key policy at all.

Think of it as

Think of a data key as the physical key to a filing cabinet, and the KMS key as a safe that stores a copy of that filing-cabinet key. You never put the whole filing cabinet inside the safe — that would be slow and the safe is small. You lock the cabinet key inside the safe, carry the locked key alongside the cabinet, and only open the safe (a single small operation) when you actually need to unlock the cabinet.

What we're doing: See encryption context enforced as authenticated data: an Encrypt call with one context, then a Decrypt call with a different context.

encryption-context-mismatch.txttext
Encrypt(plaintext, KeyId=key-1, EncryptionContext={"table": "orders"})
→ returns ciphertext bound to that exact context

Decrypt(ciphertext, EncryptionContext={"table": "invoices"})
→ FAILS — the supplied context does not match what was used to encrypt
1
The encryption context is not encrypted itself — it travels alongside the ciphertext in plaintext, and shows up in CloudTrail logs for auditing.
4
KMS treats the context as additional authenticated data: changing even one value causes decryption to fail outright, even though it was never secret.

Why this works: Encryption context lets you bind ciphertext to a specific piece of metadata (which table, which tenant) without adding a secret — a mismatch is strong evidence the ciphertext is being used somewhere it should not be, and every use of it is visible in CloudTrail for audit.

Assuming encryption context is a secret credential

Wrong

text
# "We'll put a shared secret in the encryption context so only our
# service can decrypt this."

Better

text
# Use encryption context only for non-secret binding metadata (tenant ID,
# table name) — access control comes from the key policy and IAM/grants,
# not from the context value itself

What you see: The value chosen as "secret" context shows up in plaintext in CloudTrail logs for every encrypt/decrypt call, defeating the intended confidentiality.

Why: Encryption context is authenticated but never encrypted — KMS logs it in full on every API call specifically so it is auditable, which makes it the wrong place for anything meant to stay confidential.

Key policy vs grant

Key policy

  • +One per key, always required — the baseline authorization document
  • +Edited directly; changes apply to everyone the policy names
  • +The natural place for long-lived, broad access

Grant

  • Any number per key, all optional — layered on top of the key policy
  • Created and retired independently, without touching the key policy
  • The natural place for narrow, temporary, or automated access
  • Key policy
    • One per key, always required — the baseline authorization document
    • Edited directly; changes apply to everyone the policy names
    • The natural place for long-lived, broad access
  • Grant
    • Any number per key, all optional — layered on top of the key policy
    • Created and retired independently, without touching the key policy
    • The natural place for narrow, temporary, or automated access

The three KMS key types

The three KMS key types
Key typeWho controls the key policyCross-account sharingRotation
Customer managed keyYou, fullyYes, via the key policyOptional automatic rotation, default 365 days
AWS managed keyThe AWS service (you can view, not edit)No — scoped to your account onlyAutomatic, every year, cannot be disabled
AWS owned keyThe AWS service, exclusively — not even visible to youHandled transparently by the serviceManaged entirely by the owning service

Together

text
Choosing a key type for a new S3 bucket's default encryption:
- Need to audit every use in CloudTrail and share the bucket cross-account?
  → customer managed key
- Want encryption with zero setup, single account, never plan to share it?
  → AWS managed key (alias/aws/s3) is enough

Remember: Envelope encryption: a data key encrypts your data, a KMS key encrypts the data key. Every key needs exactly one key policy; grants add temporary access without editing it. Encryption context is authenticated but not secret — it appears in CloudTrail. Customer managed keys support cross-account sharing and optional rotation (365-day default); AWS managed keys don't share cross-account and rotate automatically every year regardless.

See also: kms authorization iam and key policy · encryption at rest vs in transit

Encryption at Rest vs. Encryption in Transit

corebeginner

Encryption at rest protects data while it is stored on disk — S3 objects, EBS volumes, RDS databases — so a stolen disk or leaked storage snapshot is unreadable without the key. Encryption in transit protects data while it moves across a network, using TLS, so anyone intercepting the traffic between a client and AWS (or between two AWS services) cannot read it. They protect different moments and neither one substitutes for the other.

Think of it as

At rest is the lock on a filing cabinet — it protects the papers only while they sit in the drawer. In transit is an armored courier van — it protects the papers only while they are being driven somewhere. A cabinet with a great lock does nothing for papers being carried to it in an open envelope, and an armored van does nothing once the papers are unloaded into an unlocked cabinet. Real protection needs both.

What we're doing: See why enabling one type of encryption does not protect against the other.

at-rest-vs-in-transit.txttext
S3 bucket: SSE-KMS enabled (encryption at rest) ✓
Bucket policy: does NOT require aws:SecureTransport ✗
→ An HTTP (non-TLS) request to the bucket still succeeds — the object is
  encrypted on disk, but readable in plaintext while it travels over the wire
1
SSE-KMS only ever governs how the object is stored — it says nothing about the network connection used to fetch it.
4
Without a policy that explicitly denies non-TLS requests, an HTTP request is still served — the object decrypts server-side and is sent back over an unencrypted connection.

Why this works: Encryption at rest and encryption in transit are enforced by completely separate mechanisms — one being on says nothing about the other, so both need to be verified independently for data to actually be protected end to end.

Treating "encryption enabled" on a bucket or database as covering the whole data path

Wrong

text
# "SSE-KMS is on for this bucket, so the data is encrypted end to end."

Better

text
# Enable SSE-KMS for at-rest protection, AND add a bucket policy condition
# requiring aws:SecureTransport = true to force every request over TLS

What you see: A security review finds objects can still be fetched over plain HTTP even though "encryption" was checked off as done for the bucket.

Why: At-rest encryption only ever covers the stored copy — nothing about it constrains how a client is allowed to connect, so the in-transit leg has to be enforced as its own, separate control.

Two different moments, two different controls

At rest

  • +Protects data while stored (disk, database, object storage)
  • +Implemented with server-side encryption, usually backed by a KMS key
  • +A stolen disk or leaked snapshot is unreadable without the key

In transit

  • Protects data while moving across a network
  • Implemented with TLS, using a certificate from ACM (or another CA)
  • Intercepted traffic between a client and AWS is unreadable without the session key
  • At rest
    • Protects data while stored (disk, database, object storage)
    • Implemented with server-side encryption, usually backed by a KMS key
    • A stolen disk or leaked snapshot is unreadable without the key
  • In transit
    • Protects data while moving across a network
    • Implemented with TLS, using a certificate from ACM (or another CA)
    • Intercepted traffic between a client and AWS is unreadable without the session key

Remember: At rest protects stored data (SSE, usually backed by a KMS key); in transit protects data on the network (TLS, via ACM certificates). They are independent — one being enabled never implies the other is.

See also: kms envelope encryption and key management

Why KMS Authorization Needs Both IAM and a Key Policy

coreintermediate

A KMS key checks two separate documents before allowing any use: the key policy (attached to the key itself) and, if the key policy enables it, IAM policies (attached to the calling user or role). Unless the key policy explicitly allows it, an IAM policy alone cannot grant access — a generous IAM policy means nothing if the key policy never opens the door for IAM policies to matter.

Think of it as

A KMS key is a locked room with its own guard (the key policy) plus a building-wide badge system (IAM). The guard decides, room by room, whether badges are even checked here at all. If the guard has not opted in to trusting badges, having the right badge changes nothing — you still need the guard's own explicit sign-off.

What we're doing: See why a role with a wide-open IAM policy still cannot use a KMS key whose key policy never enabled IAM.

kms-two-layer-denial.jsonjson
// IAM policy on the role — looks fully permissive
{ "Effect": "Allow", "Action": "kms:Decrypt", "Resource": "arn:aws:kms:...:key/abcd-1234" }

// Key policy on the KMS key itself — omits the "enable IAM policies"
// statement that the AWS-generated default key policy normally includes
{ "Statement": [
  { "Effect": "Allow", "Principal": { "AWS": "arn:aws:iam::111122223333:root" },
    "Action": "kms:*" }
] }
→ Decrypt still fails for the role: the key policy names only the account
  root, and never enables IAM policies to grant anything beyond that
2
This IAM statement looks like a complete grant of kms:Decrypt — and in isolation, for most other AWS resources, it would be.
8
The key policy only allows the root user directly. It never includes the statement that lets IAM policies grant access to anyone else — so the IAM Allow above is simply never consulted.

Why this works: AWS KMS deliberately requires the resource owner (the key policy) to opt in before IAM policies are even considered — this stops a broad IAM policy elsewhere in the account from silently granting access to a sensitive key its owner never intended to share.

Debugging a KMS AccessDenied by reviewing only the caller's IAM policy

Wrong

text
# "The IAM policy attached to this role clearly allows kms:Decrypt —
# this AccessDenied doesn't make sense."

Better

text
# Check the KMS key policy too: does it name this principal (or account),
# and does it include the statement enabling IAM policies at all?

What you see: Time is spent re-checking an IAM policy that is already correct, while the actual missing piece is a key policy that never opted in to trusting IAM policies for that key.

Why: KMS evaluates the key policy first as the resource owner's decision — a correct IAM policy is necessary but never sufficient on its own, so the key policy has to be checked independently, not as an afterthought.

What each layer alone can and cannot do

Key policy alone

  • +Always sufficient by itself — every key needs exactly one
  • +Can grant access directly without any IAM policy involved
  • +Must explicitly enable IAM policies, or they are ignored entirely

IAM policy alone

  • Never sufficient by itself for granting access to a key
  • Only takes effect once the key policy has opted in to trusting IAM
  • Can still DENY access to a key even without that opt-in
  • Key policy alone
    • Always sufficient by itself — every key needs exactly one
    • Can grant access directly without any IAM policy involved
    • Must explicitly enable IAM policies, or they are ignored entirely
  • IAM policy alone
    • Never sufficient by itself for granting access to a key
    • Only takes effect once the key policy has opted in to trusting IAM
    • Can still DENY access to a key even without that opt-in

Remember: A KMS key checks the key policy first — it is required, and unless it explicitly enables IAM, an IAM Allow does nothing. IAM can still Deny access to a key without that enablement. Always check both when a KMS AccessDenied looks wrong on the IAM side.

See also: kms envelope encryption and key management · policy types and trust

Advertisement