AWS Security

AWS KMS & Encryption, In Depth: Keys, Key Policies, Envelope Encryption, Grants & Rotation

AWS Key Management Service (KMS) is the encryption backbone of AWS. Almost every “encrypt at rest” checkbox you have ever ticked — S3 buckets, EBS volumes, RDS databases, Secrets Manager secrets, DynamoDB tables, EFS file systems, CloudWatch Logs — quietly calls KMS underneath. It is a managed service that creates, stores and controls the cryptographic keys that protect your data, and it does so using hardware security modules (HSMs) that are FIPS 140-3 Security Level 3 validated (the validated HSM fleet that backs KMS), so the raw key material is generated inside tamper-resistant hardware and can never be exported in plaintext.

That last sentence is the whole game. KMS is not really a “place you send data to be encrypted” — it is an authorisation system wrapped around key material that never leaves the HSM. Once you internalise that, every other behaviour in this lesson — why there is a 4 KB limit on direct encryption, why “envelope encryption” exists, why there are three different ways to grant access to a key, why rotation is almost invisible — falls out as a logical consequence. Getting KMS right is the difference between encryption that is a compliance checkbox and encryption that actually proves only this one role, in this one account, under these one conditions could ever read the data.

This lesson is the exhaustive tour: the mental model and key types, envelope encryption byte by byte, the access-control trio (key policies, IAM and grants), rotation, encryption context, aliases, multi-Region keys, CloudHSM and the service (SSE) integrations — each in full, with the what · choices · default · when · trade-off · gotcha treatment and real aws kms commands throughout. By the end you will know every option KMS offers and when to reach for each. For the principal-level architecture view — multi-Region keys for DR, cross-account data sharing at scale and request-quota tuning — this lesson pairs with AWS KMS in Depth: Multi-Region Keys, Envelope Encryption, Key Policies, and Grants.

Learning objectives

By the end of this lesson you will be able to:

Prerequisites & where this fits

You need an AWS account and a working grasp of IAM — principals, identity policies vs resource policies, and the explicit-deny-beats-everything evaluation logic — because KMS access is decided by exactly that machinery plus a resource policy of its own (the key policy). If IAM is hazy, read AWS IAM Fundamentals first. This is the foundational Security lesson of the AWS Zero-to-Hero course, the one the secrets-management, data-protection and multi-Region lessons build on. After this, the course continues into AWS Secrets Manager vs SSM Parameter Store, which itself relies on KMS to encrypt every secret.

Core concepts: the one mental model

The single most important fact about KMS: your plaintext almost never travels to KMS, and KMS keys never leave KMS. A KMS key (the formal name; older docs and the console still say Customer Master Key / CMK, and the AWS CLI/SDK call the identifier a KeyId) is a logical container for key material that lives inside the HSM fleet. The KMS key has an ARN, an alias, a policy, metadata and rotation settings — but the actual bytes of the key are sealed in hardware and are non-exportable.

So what can you do with a key you cannot see? You can ask KMS to perform operations with it:

A few terms used throughout:

Term Meaning
KMS key (CMK) The logical key resource in KMS; holds the key material reference, policy, aliases, rotation config. Identified by a key ID (UUID), key ARN, or alias.
Key material The actual cryptographic bytes inside the HSM. Non-exportable for AWS-generated symmetric keys.
Data key (DEK) A key generated by KMS but used outside KMS (by you, in your application or by an AWS service) to encrypt actual data. Also called a data encryption key.
Key Encryption Key (KEK) A key whose only job is to encrypt other keys. A KMS symmetric key acts as the KEK that wraps your data keys.
Envelope encryption Encrypting data with a data key, then encrypting that data key with a KMS key, and storing the wrapped data key alongside the data.
Grant A temporary, programmatic permission to use a key, scoped to specific operations and (optionally) an encryption context.
Encryption context A set of non-secret key/value pairs cryptographically bound to the ciphertext as Additional Authenticated Data (AAD).
Alias A friendly, mutable pointer (alias/my-app-key) to a key ID, so applications need not hard-code the UUID.

Internalise this one line and the rest is detail: KMS is a wrapping-and-authorisation service, not a bulk cipher. Quotas, caching, multi-Region portability and the 4 KB limit are all consequences of “KMS protects the key that protects the data.”

Key types: every option

KMS keys are not interchangeable. Two properties — the key spec (the algorithm/size, set with --key-spec) and the key usage (ENCRYPT_DECRYPT, SIGN_VERIFY or GENERATE_VERIFY_MAC, set with --key-usage) — are immutable once the key is created. Choosing them is a one-way door, so this section matters.

By who manages the key

This is the axis most people mean when they say “key type”. It controls who can see the key, who pays, where it appears, and whether you can set a policy or rotation.

Key category Who creates/manages it Visible in your account? Set key policy? Rotation control Cost (storage) Use when
Customer-managed key (CMK) You Yes, full control Yes — you own the policy You choose: off, annual, custom 90–2560 days, or on-demand $1/month per key + API calls You need control: audit, your own policy, rotation, cross-account, disable/delete, grants. The default choice for anything serious.
AWS-managed key AWS (one per service per account, e.g. aws/s3, aws/rds) Yes (read-only) — you can view it and see its CloudTrail usage No — AWS owns the policy Automatic, every year, not configurable Free to store; you pay only for API calls You want “encrypted” with zero management and no cross-account/custom-policy needs.
AWS-owned key AWS, shared across many accounts No — invisible to you, no ARN, no CloudTrail in your account No AWS-controlled Free, no API charges The service default when you pick “encryption” without choosing a key; lowest effort, least control/visibility.

The trade-off ladder is: AWS-owned (least control, free, invisible) → AWS-managed (some visibility, free storage, fixed annual rotation) → customer-managed (full control, $1/month, everything configurable). The big gotcha: you cannot attach your own key policy to, disable, schedule deletion of, or share an AWS-managed or AWS-owned key. The moment a requirement says “prove only role X can decrypt”, “rotate every 90 days”, or “share with account Y”, you need a customer-managed key.

AWS-managed vs AWS-owned, the exam trap: AWS-managed keys show up in your account (named aws/<service>), generate CloudTrail events, and rotate yearly. AWS-owned keys are pooled across many AWS customers, never appear in your account, and generate no CloudTrail in your account. Both are free to store; only customer-managed keys cost $1/month.

By key spec (algorithm)

Orthogonal to who manages it is what kind of cryptography it does. Only customer-managed keys can be asymmetric or HMAC; AWS-managed keys are always symmetric ENCRYPT_DECRYPT.

Key spec (--key-spec) Type Usage Operations Can generate data keys? Use it for
SYMMETRIC_DEFAULT Symmetric (AES-256-GCM) ENCRYPT_DECRYPT Encrypt, Decrypt, GenerateDataKey* Yes The default and the workhorse — envelope encryption, all SSE integrations (S3/EBS/RDS/etc.).
RSA_2048, RSA_3072, RSA_4096 Asymmetric ENCRYPT_DECRYPT or SIGN_VERIFY Encrypt/Decrypt (with RSAES_OAEP_*) or Sign/Verify (with RSASSA_*) No Encrypting where the encryptor has no AWS credentials (you hand out the public key); RSA signing/verification.
ECC_NIST_P256, ECC_NIST_P384, ECC_NIST_P521, ECC_SECG_P256K1 Asymmetric (ECC) SIGN_VERIFY Sign, Verify No Digital signatures (smaller, faster than RSA); P256K1 for blockchain use cases.
SM2 (China Regions only) Asymmetric ENCRYPT_DECRYPT or SIGN_VERIFY Encrypt/Decrypt or Sign/Verify No Chinese national cryptographic standard, China Regions.
HMAC_224, HMAC_256, HMAC_384, HMAC_512 HMAC (symmetric) GENERATE_VERIFY_MAC GenerateMac, VerifyMac No Message authentication codes — signed tokens, deterministic integrity checks, validating data has not been tampered with, without a separate signing infra.

Three things to nail for interviews and exams:

  1. Only symmetric SYMMETRIC_DEFAULT keys support GenerateDataKey and envelope encryption. Asymmetric and HMAC keys cannot mint data keys, and the SSE service integrations (S3, EBS, RDS…) only accept symmetric keys.
  2. Asymmetric keys let you publish the public key. Anyone — even someone with no AWS account — can download your public key and encrypt or verify offline; only KMS (with the private key it never releases) can decrypt or sign. That is the entire reason asymmetric KMS keys exist: to bridge to parties outside AWS.
  3. Key usage is fixed at creation. An RSA key created for ENCRYPT_DECRYPT can never sign, and vice versa. Pick deliberately.

Data keys vs data key pairs

When you call GenerateDataKey against a symmetric KMS key you get a symmetric data key (e.g. 256-bit, for AES-GCM). KMS also offers:

The plaintext data key you receive is your responsibility to destroy — zero it from memory as soon as you have used it. The encrypted copy is what you persist.

Envelope encryption: the flow, byte by byte

Direct Encrypt/Decrypt is capped at 4 KB because every call is a network round-trip into the HSM, and KMS is not built to stream gigabytes through hardware crypto modules. Envelope encryption solves this elegantly: encrypt your data locally with a fast, local data key, and only ask KMS to encrypt the tiny data key.

Here is the full flow.

Encrypting:

  1. Your application calls GenerateDataKey(KeyId='alias/my-key', KeySpec='AES_256').
  2. KMS, inside the HSM, generates a random 256-bit data key. It returns two things:
    • Plaintext — the raw data key bytes.
    • CiphertextBlob — that same data key, encrypted under your KMS key (the KEK).
  3. Your app uses the plaintext data key with a local cipher (AES-256-GCM) to encrypt the actual payload — be it 4 KB or 4 TB. This happens at local CPU speed, no per-byte KMS calls.
  4. Your app immediately discards the plaintext data key (overwrite the variable), and stores the CiphertextBlob (the encrypted data key) alongside the encrypted data — e.g. as object metadata, a file header, or a column.

Now the encrypted data and the encrypted data key sit together. Anyone who steals the storage gets ciphertext and a wrapped key they cannot unwrap without calling KMS — and KMS will only unwrap it for an authorised principal.

Decrypting:

  1. Your app reads the stored CiphertextBlob (encrypted data key) and calls Decrypt(CiphertextBlob).
  2. KMS checks authorisation, then unwraps the data key inside the HSM and returns the plaintext data key. (You do not even have to tell Decrypt which KMS key to use for a symmetric key — KMS reads the key ID from the ciphertext blob’s metadata. You should still pass KeyId to pin it and prevent a confused-deputy style attack on an attacker-supplied blob.)
  3. Your app uses the plaintext data key locally to decrypt the payload, then discards the plaintext key again.

AWS KMS & envelope encryption

The diagram traces the whole envelope flow — GenerateDataKey returns the plaintext and wrapped data key, your app encrypts the payload locally, stores the wrapped key beside the data, and later Decrypt unwraps it — alongside the three-layer access-control model that decides whether any of those calls are allowed.

Two performance levers fall directly out of this model:

The KMS request-quota gotcha: cryptographic operations share a per-Region, per-account quota (e.g. tens of thousands of requests/second for symmetric Decrypt/GenerateDataKey, varying by Region and request type). Hot paths that call Decrypt on every request will hit ThrottlingException long before your app’s own limits. Envelope encryption + data-key caching + S3 Bucket Keys are the three standard fixes.

The access-control trio: key policies vs IAM vs grants

This is the most-tested KMS topic and the one people get most wrong. Access to a KMS key is governed by three mechanisms that work together: the key policy, IAM policies, and grants. (A fourth, VPC endpoint policies / global condition keys, can further restrict via the network.)

Rule 1 — the key policy is the root of trust

Every KMS key has exactly one resource policy, called the key policy, and it is mandatory — a key cannot exist without one. This is the foundational rule:

Nothing can use a KMS key unless the key policy allows it. Unlike S3 buckets or IAM roles, an IAM policy alone is never sufficient for KMS. The key policy must first open the door (directly, or by delegating to IAM), and only then can an IAM policy or grant grant the specific permission.

When you create a key without specifying a policy, KMS attaches a default key policy. The critical statement in it is:

{
  "Sid": "Enable IAM User Permissions",
  "Effect": "Allow",
  "Principal": { "AWS": "arn:aws:iam::111122223333:root" },
  "Action": "kms:*",
  "Resource": "*"
}

That Principal: ...:root does not mean “the root user”. It means “the account 111122223333” — and it is what delegates authorisation to IAM. With this statement present, the effective permission a principal has is decided by IAM as usual (identity policies, permission boundaries, SCPs). Remove this statement and IAM policies stop working for the key entirely — only principals named directly in the key policy can use it. (This is also the classic way people lock themselves out of a key — see Troubleshooting.)

So there are two governance styles:

Style Key policy says Access actually controlled by Best for
Delegate to IAM (default) Allow root: kms:* IAM identity policies (plus SCP/boundaries) Most accounts — manage key access the same way you manage everything else.
Key-policy-centric (no broad root delegation) Names specific principals/roles explicitly The key policy only High-assurance keys where you want the key’s own document to be the single auditable source of “who can use this”.

Rule 2 — IAM policies grant, but only within what the key policy delegated

An IAM identity policy can Allow kms:Decrypt on a key ARN, but it only takes effect if the key policy delegates to IAM (the root statement above) or otherwise allows that principal. IAM is how you scale access across many keys and many principals without editing every key policy. Standard pattern:

{
  "Effect": "Allow",
  "Action": ["kms:Encrypt", "kms:Decrypt", "kms:GenerateDataKey", "kms:DescribeKey"],
  "Resource": "arn:aws:kms:eu-west-1:111122223333:key/<key-id>"
}

Use kms:ViaService and other condition keys to tighten it (e.g. only allow the key to be used through S3, not directly).

Rule 3 — grants are temporary, programmatic, fine-grained permissions

A grant is a way to give a principal specific operations on a key, often temporarily, without editing the key policy or an IAM policy. Grants are the right tool when an AWS service or another principal needs just-in-time, narrowly-scoped access — for example, EBS needs to use your key to encrypt a volume, or Lambda needs to decrypt under one specific encryption context.

Property Grant
Granted operations A subset: Encrypt, Decrypt, GenerateDataKey*, CreateGrant, RetireGrant, DescribeKey, etc.
Scope-down Can be constrained by encryption context (GrantConstraints) and to a single grantee principal.
Lifetime Until retired (RetireGrant) or revoked (RevokeGrant); can also carry constraints that limit use.
Consistency Eventually consistent — a brand-new grant may take a moment to take effect; pass the returned grant token on the next call to use it immediately.
Who uses them AWS services (EBS, RDS, Redshift, etc.) create grants on your behalf when you attach your CMK; you can also create them directly.

The three combine with this evaluation logic: a request is allowed if it is permitted by the key policy and (IAM policy or a grant) — and is never allowed if any applicable policy has an explicit Deny. Explicit deny always wins, exactly as in core IAM.

The interview one-liner: “For KMS, the key policy is the root of trust — it must allow access before an IAM policy or grant can. IAM scales access across keys; grants give temporary, narrowly-scoped, often service-driven access; and explicit deny beats everything.”

Encryption context: authenticated, auditable, free

Encryption context is a set of non-secret key/value string pairs you pass to Encrypt/GenerateDataKey and must pass identically to Decrypt. KMS binds it to the ciphertext as Additional Authenticated Data (AAD) in AES-GCM, so:

It costs nothing and adds both a safety interlock and an authorisation dimension. Many AWS services set encryption context for you automatically (e.g. S3 uses the object ARN; Secrets Manager uses the secret ARN). Never put secrets in the encryption context — it is plaintext in logs.

Aliases: indirection you should always use

An alias is a friendly, mutable display name and pointer to a key, in the form alias/my-app-key. Applications reference alias/my-app-key instead of the raw key UUID, so you can re-point the alias to a different key (for example after creating a new key) without changing application code.

Property Detail
Format alias/<name>; the aws/<service> prefix is reserved for AWS-managed keys.
Scope Region-specific; an alias maps to one key in that Region.
Mutability You can update which key an alias points to, and delete aliases, freely.
Permissions You can scope IAM with kms:RequestAlias / kms:ResourceAliases conditions.
Gotcha An alias is not a security boundary — permissions are evaluated against the underlying key ARN, not the alias. Re-pointing an alias does not re-encrypt existing data; old ciphertext still references the old key ID embedded in its blob.

Use aliases everywhere in application config; they make key rotation-by-replacement and environment promotion (dev/stage/prod) painless.

Key rotation: what it does and does not change

Rotation replaces the cryptographic key material, while the key ID, ARN, aliases, policies and grants stay the same. Crucially, KMS keeps all old versions of the key material so that data encrypted under a previous version can still be decrypted transparently — rotation never re-encrypts your existing data, and you never have to track which version encrypted what.

Rotation mode How it works Applies to Notes
Automatic rotation KMS generates new material on a schedule; old material retained for decrypt Customer-managed symmetric keys (and AWS-managed keys do this automatically) Configurable period: default 365 days, settable 90–2560 days. Enable with enable-key-rotation --rotation-period-in-days.
On-demand rotation You trigger a rotation immediately with rotate-key-on-demand Customer-managed symmetric keys Up to a limited number of on-demand rotations per key; great for “rotate now” after an incident.
AWS-managed key rotation Automatic, every year, not configurable AWS-managed keys (aws/<service>) You cannot change or disable it.
Manual rotation (rotate by replacement) You create a new key, then re-point the alias to it Any key, including asymmetric, HMAC and imported-material keys (which cannot auto-rotate) Old key must be kept enabled to decrypt old data. This is the only rotation option for asymmetric/HMAC/imported keys.

Key facts that trip people up:

Imported key material (BYOK): you can create a key with no AWS-generated material and import your own 256-bit material (wrapped for transport). You then control the material and can set an expiration; you must re-import to “rotate”. Trade-off: you own the material lifecycle (and the risk of losing it) and lose automatic rotation. Use only when a regulation demands you generate keys outside AWS.

Multi-Region keys: ciphertext that travels

A normal KMS key is Regional — its material never leaves its Region, and ciphertext produced in eu-west-1 can only be decrypted by calling KMS in eu-west-1. That breaks cross-Region DR and global datastores.

Multi-Region keys solve this: you create a primary key in one Region and replicate it to others. The replicas share the same key material and the same key ID suffix (the ARN differs only by Region), so ciphertext encrypted in one Region can be decrypted by the replica in another Region without a cross-Region call.

Aspect Multi-Region key behaviour
Key material Shared across all replica Regions (the only KMS keys for which material crosses a Region boundary, securely).
Key ID Shared suffix (mrk-...); ARNs differ only by Region.
Policies/grants/aliases Independent per Region — you manage each replica’s policy separately.
Rotation Managed on the primary and propagated; replicas stay in sync.
Primary Exactly one primary at a time; you can update which Region is primary.
When to use Cross-Region DR, DynamoDB global tables, S3 Cross-Region Replication with KMS, active-active multi-Region apps.
When not to If you do not need cross-Region ciphertext portability — regional keys have a tighter blast radius and are simpler.

The deeper architecture — replication mechanics, failover and cross-account multi-Region sharing — is covered in AWS KMS in Depth: Multi-Region Keys, Envelope Encryption, Key Policies, and Grants.

CloudHSM vs KMS (and the Custom Key Store)

KMS uses HSMs under the hood but presents a fully managed, multi-tenant, API-first service. AWS CloudHSM gives you single-tenant, dedicated HSMs that you administer, in your VPC, with FIPS 140-3 Level 3 validation and sole control of the keys (AWS cannot access them).

Dimension AWS KMS AWS CloudHSM
Tenancy Multi-tenant managed service Single-tenant dedicated HSM cluster (in your VPC)
Who controls keys AWS operates the HSMs; you control via policy You fully control; AWS has no access
Integration with AWS services Native (S3/EBS/RDS/etc. integrate directly) Not native — you integrate via PKCS#11/JCE/KSP, or via a KMS Custom Key Store
Management effort Minimal You manage cluster, users, backups, HA
Compliance FIPS 140-3 Level 3 (validated HSM fleet) FIPS 140-3 Level 3, dedicated
Cost model $1/key/month + API calls Per-HSM-hour (substantially more expensive)
Use when Default for nearly all AWS encryption Regulatory mandate for sole custody / dedicated HSM, or you need HSM features KMS does not expose

The KMS Custom Key Store is the bridge: a KMS key store backed by your CloudHSM cluster (or by an external key store, XKS, backed by an HSM you run outside AWS). You get the KMS API and native service integrations (so S3/EBS just work) while the key material lives in your dedicated CloudHSM / external HSM. It is the way to satisfy “keys must live in our own HSM” and “we still want SSE-KMS for S3” at the same time — at higher cost and operational burden.

Interview answer: “KMS is the managed, multi-tenant, natively-integrated default. CloudHSM is single-tenant, customer-controlled, dedicated hardware for sole-custody regulatory needs. When you need both — your own HSM and native AWS integration — use a KMS Custom Key Store backed by CloudHSM (or XKS for an external HSM).”

SSE integrations: how S3, EBS and RDS use KMS

“Server-Side Encryption (SSE)” means an AWS service encrypts your data at rest for you, calling KMS via envelope encryption behind the scenes. The patterns are consistent across services.

Amazon S3 — four options:

S3 option Key used KMS calls Notes
SSE-S3 (AES256) AWS-owned key managed by S3 None you pay for The default; zero config, free.
SSE-KMS (aws:kms) AWS-managed aws/s3 or your CMK Yes (one per object unless Bucket Keys on) Audit in CloudTrail, key policy control. Enable S3 Bucket Keys to slash KMS calls/cost.
DSSE-KMS (aws:kms:dsse) Your CMK Yes Dual-layer KMS encryption for stringent compliance (two independent layers).
SSE-C A key you supply on each request None (you hold the key) You manage the key entirely; AWS never stores it.

All new S3 objects are encrypted by default (SSE-S3 at minimum); set a bucket default encryption to force SSE-KMS, and deny unencrypted/PutObject-without-aws:kms via a bucket policy.

Amazon EBS — encryption is a volume property chosen at creation; you pick SSE-S3-style platform default or your CMK. Behind the scenes EBS calls GenerateDataKey (via a grant on your key) to get a volume data key, and every snapshot and volume created from an encrypted volume is also encrypted. You can set EBS encryption by default per-Region so every new volume is encrypted. You cannot encrypt an existing unencrypted volume in place — you snapshot, copy the snapshot with encryption, and restore.

Amazon RDS / Aurora — encryption is enabled at instance/cluster creation with an AWS-managed key or your CMK, and it covers the underlying storage, automated backups, read replicas and snapshots. The key fact: you cannot enable or disable encryption on an existing unencrypted RDS instance — you must snapshot → copy snapshot with encryption → restore. A read replica must use a key in its own Region (multi-Region keys help cross-Region replicas).

The common thread: every one of these is envelope encryption — the service asks KMS for a data key (often via a grant), encrypts your bytes locally with it, stores the wrapped key, and decrypts on read. You control which key and who can use it; the service does the plumbing.

Hands-on lab: envelope encryption from the CLI

This lab creates a customer-managed key, exercises direct and envelope encryption, sets an alias, enables rotation, then cleans up. It stays within the AWS Free Tier in spirit but note the cost caveat below. Requires the AWS CLI v2 configured with credentials that can manage KMS.

1. Create a customer-managed symmetric key and capture its ID:

KEY_ID=$(aws kms create-key \
  --description "kv-lab envelope encryption demo" \
  --key-usage ENCRYPT_DECRYPT \
  --key-spec SYMMETRIC_DEFAULT \
  --query 'KeyMetadata.KeyId' --output text)
echo "Key: $KEY_ID"

2. Give it a friendly alias:

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

3. Direct encryption (small data, under 4 KB):

aws kms encrypt --key-id alias/kv-lab \
  --plaintext fileb://<(echo -n "top secret string") \
  --query CiphertextBlob --output text > secret.b64

# Decrypt it back
aws kms decrypt --ciphertext-blob fileb://<(base64 --decode secret.b64) \
  --key-id alias/kv-lab \
  --query Plaintext --output text | base64 --decode; echo
# Expected output: top secret string

4. Envelope encryption (generate a data key, encrypt a large file locally):

# Make a "large" file
head -c 1M /dev/urandom > bigfile.bin

# Ask KMS for a data key: plaintext + wrapped copy
aws kms generate-data-key --key-id alias/kv-lab --key-spec AES_256 \
  --query '[Plaintext,CiphertextBlob]' --output text > dk.txt
PLAIN_DK=$(cut -f1 dk.txt); WRAPPED_DK=$(cut -f2 dk.txt)

# Encrypt the big file locally with the plaintext data key (OpenSSL)
echo "$PLAIN_DK" | base64 --decode > dk.bin
openssl enc -aes-256-cbc -pbkdf2 -in bigfile.bin -out bigfile.enc -pass file:dk.bin
# Store ONLY the wrapped data key alongside the ciphertext; discard plaintext key
shred -u dk.bin 2>/dev/null || rm -f dk.bin
echo "$WRAPPED_DK" > bigfile.key

5. Decrypt the envelope (unwrap the data key, then decrypt locally):

aws kms decrypt --ciphertext-blob fileb://<(base64 --decode bigfile.key) \
  --key-id alias/kv-lab --query Plaintext --output text | base64 --decode > dk.bin
openssl enc -d -aes-256-cbc -pbkdf2 -in bigfile.enc -out bigfile.dec -pass file:dk.bin
cmp bigfile.bin bigfile.dec && echo "MATCH: envelope round-trip works"
rm -f dk.bin
# Expected output: MATCH: envelope round-trip works

6. Encryption context (must match on decrypt):

aws kms encrypt --key-id alias/kv-lab \
  --plaintext fileb://<(echo -n "tenant data") \
  --encryption-context tenant=acme,app=billing \
  --query CiphertextBlob --output text > ctx.b64

# Wrong context FAILS:
aws kms decrypt --ciphertext-blob fileb://<(base64 --decode ctx.b64) \
  --encryption-context tenant=globex 2>&1 | grep -i invalid || echo "(expected an InvalidCiphertext error)"

# Correct context SUCCEEDS:
aws kms decrypt --ciphertext-blob fileb://<(base64 --decode ctx.b64) \
  --encryption-context tenant=acme,app=billing \
  --query Plaintext --output text | base64 --decode; echo
# Expected output: tenant data

7. Enable automatic rotation (90 days):

aws kms enable-key-rotation --key-id "$KEY_ID" --rotation-period-in-days 90
aws kms get-key-rotation-status --key-id "$KEY_ID"
# Expected: "KeyRotationEnabled": true, "RotationPeriodInDays": 90

Validation: confirm the key, alias and rotation:

aws kms describe-key --key-id alias/kv-lab --query 'KeyMetadata.[KeyId,KeyState,KeySpec,KeyUsage]' --output text
aws kms list-aliases --key-id "$KEY_ID" --query 'Aliases[].AliasName' --output text

Cleanup: you cannot delete a KMS key instantly — you schedule deletion with a mandatory waiting period of 7 to 30 days (default 30), during which the key is PendingDeletion and unusable but recoverable with cancel-key-deletion. Delete the alias immediately; schedule the key for the minimum 7-day window:

aws kms delete-alias --alias-name alias/kv-lab
aws kms schedule-key-deletion --key-id "$KEY_ID" --pending-window-in-days 7
rm -f secret.b64 bigfile.bin bigfile.enc bigfile.dec bigfile.key dk.txt ctx.b64

Cost note: a customer-managed key costs $1/month (prorated — even pending-deletion keys incur the charge until actually deleted, so schedule deletion promptly). API calls are billed per 10,000 requests (the first 20,000/month are free in many Regions); this lab’s handful of calls is effectively free. The $1/month is the line item to remember.

Common mistakes & troubleshooting

Symptom Likely cause Fix
AccessDeniedException on kms:Decrypt despite a permissive IAM policy The key policy does not delegate to IAM (no Allow root: kms:* statement) Add the IAM-delegation statement to the key policy, or name the principal directly in the key policy. IAM alone is never enough for KMS.
You removed the root statement and now no one can manage the key Locked yourself out — IAM no longer applies and no principal is named Open a support case to recover, or prevent it: always keep at least one admin principal in the key policy. (This is why the console warns you.)
InvalidCiphertextException on decrypt Encryption context mismatch, or you passed a ciphertext from a different key, or the blob is corrupted Pass the exact same encryption context used at encrypt; verify the KeyId.
ThrottlingException / KMSThrottlingException on a hot path Exceeded the per-Region request quota with per-request Decrypt/GenerateDataKey Use envelope encryption + data-key caching, enable S3 Bucket Keys, or request a quota increase.
EBS volume creation fails with KMS permission error The service lacks a grant on your CMK, or the role can’t CreateGrant Ensure the key policy/IAM allows kms:CreateGrant; let the service create its grant.
Cannot enable automatic rotation on a key The key is asymmetric, HMAC, multi-Region with imported material, or has imported material These cannot auto-rotate — rotate manually by creating a new key and re-pointing the alias.
Re-pointed an alias but old data still decrypts with the old key Ciphertext blobs embed the original key ID Expected — aliases don’t re-encrypt. To fully migrate, re-encrypt the data under the new key (ReEncrypt).
Cross-account principal can’t use your key Cross-account needs both sides: your key policy must allow the external account/role and that account’s IAM must allow it Allow the external principal in the key policy and have their admin grant the IAM permission; consider a grant for narrow, temporary access.

Best practices

Security notes

Interview & exam questions

  1. What is envelope encryption and why does KMS use it? KMS generates a data key, returns it in plaintext and encrypted-under-the-KMS-key. You encrypt your data locally with the plaintext data key, discard it, and store the encrypted data key beside the data. KMS only ever wraps the small data key, so you can encrypt unlimited data without pushing it (or exceeding the 4 KB limit) through KMS.

  2. Key policy vs IAM policy vs grant — how do they interact? The key policy is the mandatory root of trust and must allow access first. IAM policies grant access at scale but only work if the key policy delegates to IAM (the Allow root: kms:* statement). Grants give temporary, narrowly-scoped, often service-driven permissions. A request is allowed if the key policy and (IAM or grant) allow it, and explicit deny always wins.

  3. AWS-managed vs AWS-owned vs customer-managed keys? AWS-owned are pooled across customers, invisible in your account, free, no CloudTrail. AWS-managed (aws/<service>) are in your account (read-only), free to store, auto-rotate yearly, generate CloudTrail. Customer-managed cost $1/month and give full control: your policy, rotation schedule, disable/delete, grants, cross-account.

  4. What does key rotation actually change, and what stays the same? It replaces the key material while keeping the key ID, ARN, aliases, policies and grants unchanged, and retains old material so existing ciphertext still decrypts. It does not re-encrypt your data. Only symmetric, AWS-generated keys can auto-rotate; asymmetric/HMAC/imported keys must be rotated by replacement.

  5. Why is there a 4 KB limit on Encrypt, and how do you encrypt a 5 GB file? Direct Encrypt round-trips into the HSM and is capped at 4 KB. For large data, call GenerateDataKey, encrypt locally with the data key (envelope encryption), and store the wrapped key with the file.

  6. What is encryption context and why use it? Non-secret key/value pairs bound to the ciphertext as AAD. They must match exactly on decrypt (else it fails), are logged in CloudTrail for audit, and can be required in policies/grants as an extra authorisation dimension. Never put secrets in it.

  7. When would you choose CloudHSM over KMS? When you need single-tenant, dedicated, customer-controlled HSMs (sole custody, AWS has no access) for a regulatory mandate, or HSM features KMS doesn’t expose. To keep native AWS integrations while material lives in CloudHSM, use a KMS Custom Key Store.

  8. How do multi-Region keys work and when do you need them? A primary key replicated to other Regions, sharing key material and key-ID suffix, so ciphertext from one Region decrypts in another without a cross-Region call. Policies/grants/aliases are per-Region. Use for cross-Region DR, DynamoDB global tables, and active-active apps.

  9. How do you enable encryption on an existing unencrypted EBS volume or RDS instance? You cannot encrypt in place. Snapshot → copy the snapshot with encryption (specifying the KMS key) → create the new encrypted volume/restore the instance from the encrypted snapshot.

  10. What’s the difference between GenerateDataKey and GenerateDataKeyWithoutPlaintext? GenerateDataKey returns the data key both in plaintext (use now) and encrypted (store). GenerateDataKeyWithoutPlaintext returns only the encrypted copy — you decrypt it later, at point of use, to minimise how long plaintext key material exists in memory.

  11. A role has kms:Decrypt in its IAM policy but gets AccessDenied. Why? The key policy doesn’t allow that principal — either it lacks the Allow root: kms:* IAM-delegation statement, or there’s an explicit deny, or a kms:ViaService/encryption-context condition isn’t met. IAM alone is never sufficient for KMS.

  12. What happens when you delete a KMS key, and how is data protected from accidental loss? You schedule deletion with a 7–30 day waiting period (default 30); the key becomes PendingDeletion and unusable but recoverable via cancel-key-deletion. After the window, the key and all data encrypted under it become permanently unrecoverable — hence the mandatory wait and the advice to disable first to test.

Quick check

  1. True or false: an IAM policy alone is sufficient to allow a principal to use a KMS key.
  2. Which single API call gives you a key you can use locally to encrypt a large file, plus a wrapped copy to store?
  3. Which key types cannot be rotated automatically?
  4. What does the Principal: ...:root statement in the default key policy actually authorise?
  5. Which S3 setting collapses thousands of per-object KMS calls into a few to cut cost?

Answers

  1. False. The key policy must allow access first (directly or by delegating to IAM); IAM alone is never enough for KMS.
  2. GenerateDataKey — it returns the data key in plaintext (encrypt locally, then discard) and encrypted under your KMS key (store alongside the data).
  3. Asymmetric keys, HMAC keys, and keys with imported material (BYOK) — these must be rotated manually by creating a new key and re-pointing the alias. Only symmetric, AWS-generated keys auto-rotate.
  4. It delegates authorisation to the account’s IAM — it means “account 111122223333”, enabling IAM identity policies to govern the key. It does not mean only the root user.
  5. S3 Bucket Keys — S3 uses a short-lived bucket-level data key to protect many objects, cutting SSE-KMS calls (and cost) dramatically.

Exercise

Create a customer-managed symmetric key with separate administrator and user roles baked into the key policy (administrators may manage the key but not Decrypt; users may Encrypt/Decrypt but not change the policy). Then:

  1. Enable automatic rotation with a 180-day period and confirm with get-key-rotation-status.
  2. Encrypt a string with an encryption context of project=kv-exercise, and prove that decryption fails with a different context and succeeds with the matching one.
  3. Add an IAM policy to a test role that allows kms:Encrypt/kms:Decrypt on the key only via S3 using the kms:ViaService condition, and reason about why a direct CLI decrypt from that role would be denied.
  4. Finally, disable the key, confirm encryption operations fail with the key disabled, re-enable it, then schedule deletion with the minimum 7-day window and delete the alias.

Write down, in your own words, why the key policy had to grant access before the IAM policy could take effect.

Certification mapping

Glossary

Next steps

AWSKMSEncryptionKey PoliciesEnvelope EncryptionSecurity
Need this built for real?

Vinod is a Senior Cloud Architect (22+ yrs) — available for Azure / AWS / GCP architecture, landing zones, and migrations.

Work with me

Comments

Keep Reading