1 INVOICE · 1 KEY · 7 YEARS · 20 KMS CALLS

AWS KMS:
how one invoice stays encrypted for seven years

ONE INVOICE · INV-0042.pdf· ledger-prod · us-east-1 ·
KMS calls made for it·····································20
WHO MADE THEM
S3, acting for a person or role·····································9
people managing the key·····································8
KMS itself, on a schedule·····································3
calls KMS refused·····································3

AWS Key Management Service holds the keys that encrypt your data in S3, EBS, RDS and most other AWS services, and it decides who may use them. This tutorial follows one invoice PDF, from its upload in November 2026 to the day its key is deleted in 2034, through every KMS call made for it. Each call is a CloudTrail event, and the tutorial counts all 20, including the 3 that KMS refused.

The scenes play themselves, so watch or just scroll. are tappable. Every price is us-east-1, November 2026, and the exam angle is AWS Certified Security Specialty (SCS-C03).

Every byte is already encrypted. The question is whose key.

Since 5 January 2023, every new object written to Amazon S3 has been encrypted, whether or not anyone asked for it. The choice that remains is whose key does the encrypting, and that choice decides who can read the data, who can audit each read, and who can delete the key. The counter this tutorial keeps, KMS calls made for one invoice, starts at 0 because the default makes none. By the end of this chapter it is at 2.

STEP 01 / 04

ENCRYPTED BEFORE ANYONE ASKED

S3
encrypted by default
yes, every new object since 2023-01-05
key used
S3-managed keys (SSE-S3)
can be turned off
no
DynamoDB
encrypted by default
yes, every table
key used
AWS owned key
can be turned off
no
EBS
encrypted by default
only if the Region’s setting is on
key used
aws/ebs or your key
can be turned off
yes, per Region
RDS
encrypted by default
only if chosen at creation
key used
aws/rds or your key
can be turned off
fixed when created
Fig 0.1Read each card’s last row: S3 and DynamoDB can’t turn encryption off, while EBS and RDS wait for a setting.

Amazon S3 applies SSE-S3, server-side encryption with keys S3 manages itself, to every new object written since 5 January 2023. The cipher is AES-256, it costs nothing, and default encryption can no longer be removed from any bucket. Objects stored before that date were left as they were.

DynamoDB encrypts every table at rest and has no switch to turn that off. EBS and RDS are the opt-in pair. EBS has an account setting, encryption by default, that applies to one Region at a time. RDS takes the decision when a database is created, and keeps it.

STEP 02 / 04

THREE OWNERS, ONE ALGORITHM

AWS owned
in your account
no
logged in CloudTrail
no
edit the key policy
no
rotation
set by the service
delete
no
monthly fee
$0
AWS managed · aws/s3
in your account
yes
logged in CloudTrail
yes
edit the key policy
no
rotation
every year, fixed
delete
no
monthly fee
$0
customer managed
in your account
yes
logged in CloudTrail
yes
edit the key policy
yes
rotation
90–2,560 days, or off
delete
yes, after 7–30 days
monthly fee
$1
Fig 0.2Follow the “edit the key policy” row across the cards: only the customer managed key says yes, and chapter 04 needs it.

Every does the same work: AES-256-GCM, run inside the same hardware. What differs is who owns the key. An belongs to the service and is shared across many accounts. It is free, you cannot see it, and it writes nothing to your CloudTrail.

An , such as aws/s3 or aws/ebs, sits in your account, where you can see and audit it. You cannot edit its key policy, it rotates every year, and it has no monthly fee. AWS stopped creating this type for new services in 2021.

A costs $1 a month and is yours to configure: its policy, its rotation, its deletion. Exam questions and older documentation call it a CMK, short for customer master key, a name AWS dropped in 2021.

STEP 03 / 04

A FILE WRITTEN WITH ZERO KMS CALLS

SSE-S3 · S3-MANAGED KEYS
readera role in the account
s3:GetObjectIAM and bucket policy
bytesINV-0042.pdf
1 permission · 0 KMS calls
SSE-KMS · alias/invoices-2026
readera role in the account
s3:GetObjectIAM and bucket policy
kms:Decryptkey policy can refuse
bytesINV-0042.pdf
2 permissions · 1 KMS call per read
Fig 0.3Compare the two paths: with SSE-S3 the S3 permission is the only check, with SSE-KMS the accent kms:Decrypt check runs after it.

Under SSE-S3, a PutObject makes no request to KMS at all. S3 manages those keys internally, so the upload leaves no KMS event in CloudTrail, and no key permission exists that could refuse a reader. The only permission between a role and the bytes is s3:GetObject.

SSE-KMS adds a second, independent check. To return an object, S3 calls kms:Decrypt as the reader, and the key’s policy can refuse that call even when the bucket allowed the read. That second check is what the dollar a month in the next step pays for.

STEP 04 / 04

A DOLLAR A MONTH FOR A SECOND PERMISSION

alias/invoices-2026
KeyId
mrk-1234abcd12ab34cd56ef1234567890ab
KeyManager
CUSTOMER
Origin
AWS_KMS
MultiRegion
true
$1 a month, prorated hourly
CLOUDTRAIL · ledger-prod · us-east-1
  1. #1CreateKeykey-adminok2026-11-05 16:00:00 · us-east-1
  2. #2CreateAliaskey-adminok2026-11-05 16:00:04 · us-east-1
Σ KMS calls so far2
Fig 0.4The accent rows are the invoice’s first two CloudTrail events. The total under them is this tutorial’s counter.

At 16:00 on 5 November 2026, the role key-admin calls CreateKey: a symmetric key (SYMMETRIC_DEFAULT), multi-Region, with origin AWS_KMS, meaning KMS generates the key material itself. Four seconds later CreateAlias names it alias/invoices-2026. These are calls #1 and #2.

The key costs $1 a month, prorated by the hour, however much data it encrypts. Requests are billed separately: the first 20,000 a month are free across all Regions, then every 10,000 cost $0.03. This chapter adds 2 calls, and Σ KMS calls is now 2.

CALLS #1 AND #2: THE KEY AND ITS ALIAS
aws kms create-key \
  --multi-region \
  --description "2026 invoices" \
  --tags TagKey=retention,TagValue=2033-12-31

# From the response (call #1):
#   "KeyId": "mrk-1234abcd12ab34cd56ef1234567890ab",
#   "KeyManager": "CUSTOMER",
#   "Origin": "AWS_KMS",
#   "KeySpec": "SYMMETRIC_DEFAULT"

aws kms create-alias \
  --alias-name alias/invoices-2026 \
  --target-key-id mrk-1234abcd12ab34cd56ef1234567890ab   # call #2

WHAT YOU NOW KNOW

  • S3 and DynamoDB encrypt everything by default; what is left to choose is whose key, and SSE-S3 makes zero KMS calls.
  • AWS owned, AWS managed and customer managed keys run the same algorithm; only a customer managed key lets you edit its policy.
  • invoices-2026 costs $1 a month and starts the counter at 2 calls: CreateKey and CreateAlias.
deep dive: SSE-S3, SSE-KMS, DSSE-KMS and SSE-C side by side

S3 offers four server-side options. SSE-S3 is free and makes no KMS calls. SSE-KMS encrypts under a KMS key and logs a KMS event for every request. DSSE-KMS, added in June 2023, applies two layers of encryption and does not support S3 Bucket Keys. SSE-C has you send your own key with every request.

Since April 2026, SSE-C is disabled by default on new buckets, and S3 rejects an SSE-C upload with a 403 until the bucket’s encryption configuration enables it again. AWS’s own advice is to leave it off unless a workload needs it.

Default encryption only covers new writes. S3 Inventory reports each object’s encryption status, and S3 Batch Operations can copy each older object onto itself with the new setting. The EBS setting is per Region, so a Region you have never used starts with encryption by default off. AWS owned keys write no CloudTrail events, which rules them out wherever a compliance rule demands key-usage logs.

ON THE EXAM

  • “must audit every use of the key” → a customer managed key (AWS owned keys log nothing)
  • “encrypt at rest with the least operational effort” → the service’s default encryption
  • “encrypt existing unencrypted objects” → an S3 Batch Operations copy, because default encryption only covers new writes
  • “who can edit the key policy of aws/s3?” → nobody: AWS writes it, and it can’t be changed

The KMS key never touches the invoice

KMS will encrypt at most 4,096 bytes in one request, and INV-0042.pdf is 188,416 bytes. S3 gets around the limit with a second key, made fresh for this one object, and a single KMS call that protects it. This chapter follows the upload and the first read, and Σ KMS calls goes from 2 to 4.

STEP 01 / 05

4,096 BYTES IS THE WHOLE BUDGET

Encrypt limit4,096 bytes
INV-0042.pdf188,416 bytes
the invoice is 46× what one Encrypt request accepts
Fig 1.1The short accent bar is everything one Encrypt request accepts. The long bar is the invoice, at the same scale.

The KMS Encrypt operation accepts at most 4,096 bytes of plaintext with a symmetric key. The invoice is 188,416 bytes, 46 times that. A KMS key’s material never leaves KMS’s in plaintext, so the key cannot travel to the file either.

Those HSMs are FIPS 140-3 Level 3 validated, under NIST certificate #4884 from 18 November 2024. The way out of the limit is to ask KMS for a new key instead of sending it the file, and to do the bulk encryption inside S3.

STEP 02 / 05

ONE CALL, TWO COPIES OF THE SAME KEY

  1. invoice-appPutObject · 188,416 bytes
  2. S3ledger-invoices
  3. KMSalias/invoices-2026
invoice-app sends PutObject with the PDF
from KMS
plaintext data key · 32 bytes
encrypted data key
AES-256-GCMplaintext key + INV-0042.pdf
stored in ledger-invoicesciphertext · 188,416 bytes+ encrypted data key
Fig 1.2Follow the two key chips back from KMS: the plaintext one is used once and dropped; the encrypted one is stored.

At 09:00:00 on 6 November, invoice-app uploads the PDF with PutObject. S3 calls GenerateDataKey on alias/invoices-2026 with KeySpec AES_256, which is call #3. KMS returns a 32-byte twice: once in plaintext, and once encrypted under invoices-2026.

S3 encrypts the PDF with AES-256-GCM using the plaintext copy, removes that copy from memory, and stores the encrypted copy alongside the object. AWS calls this : the encrypted data key is written on the outside of the envelope, where anyone can read it and only a kms:Decrypt call can use it.

WHAT S3 DOES FOR EVERY PUTOBJECT, WRITTEN OUT WITH BOTO3
import os
import boto3
from cryptography.hazmat.primitives.ciphers.aead import AESGCM

kms = boto3.client("kms", region_name="us-east-1")
arn = "arn:aws:s3:::ledger-invoices/2026/11/INV-0042.pdf"
pdf = open("INV-0042.pdf", "rb").read()  # 188,416 bytes

resp = kms.generate_data_key(  # one KMS call, returns the key twice
    KeyId="alias/invoices-2026",
    KeySpec="AES_256",  # 32 bytes
    EncryptionContext={"aws:s3:arn": arn},
)
nonce = os.urandom(12)
ciphertext = AESGCM(resp["Plaintext"]).encrypt(nonce, pdf, None)
del resp["Plaintext"]  # the plaintext key is used once, then dropped

stored = {"body": nonce + ciphertext, "data_key": resp["CiphertextBlob"]}
STEP 03 / 05

WHAT S3 ACTUALLY STORES

2026/11/INV-0042.pdf
ServerSideEncryptionaws:kms
SSEKMSKeyId…:key/mrk-1234…
BucketKeyEnabledfalse
ciphertext · 188,416 bytes
stored with the object, never returned by S3
encrypted data key, opened
key referencearn:aws:kms:us-east-1:111122223333:key/mrk-1234…
ciphertextthe 32-byte data key, encrypted
Decrypt reads the reference, so it needs no KeyId
Fig 1.3The accent field inside the encrypted data key is the key reference Decrypt reads to find invoices-2026 on its own.

The stored object is the ciphertext plus metadata. HeadObject returns three encryption fields: ServerSideEncryption: aws:kms, SSEKMSKeyId with the key’s ARN, and BucketKeyEnabled: false. The encrypted data key is stored with the object too, but S3 never returns it.

That encrypted data key holds more than 32 encrypted bytes. KMS writes a reference to the key that encrypted it into the ciphertext, which is why Decrypt with a symmetric key needs no KeyId parameter: KMS reads the reference and finds invoices-2026 by itself.

THE ENCRYPTION HEADERS ON INV-0042
aws s3api head-object \
  --bucket ledger-invoices \
  --key 2026/11/INV-0042.pdf

# From the response:
#   "ContentLength": 188416,
#   "ServerSideEncryption": "aws:kms",
#   "SSEKMSKeyId": "arn:aws:kms:us-east-1:111122223333:key/mrk-1234abcd12ab34cd56ef1234567890ab",
#   "BucketKeyEnabled": false
# The encrypted data key is stored with the object; S3 never returns it.
STEP 04 / 05

THE OBJECT NAME TRAVELS WITH EVERY CALL

CLOUDTRAIL RECORD · CALL #3
eventTime
2026-11-06T09:00:00Z
eventSource
kms.amazonaws.com
eventName
GenerateDataKey
managementEvent
true
userIdentity.arn
…:assumed-role/invoice-app/…
userIdentity.invokedBy
s3.amazonaws.com
requestParameters
keyId
arn:aws:kms:us-east-1:111122223333:key/mrk-1234…
keySpec
AES_256
encryptionContext
aws:s3:arn
arn:aws:s3:::ledger-invoices/2026/11/INV-0042.pdf
Fig 1.4The accent block is the encryption context: it names the object, and it appears unencrypted in every CloudTrail event.

Every KMS call S3 makes for this object carries an : the key aws:s3:arn paired with the object’s ARN. KMS binds the pair to the ciphertext as additional authenticated data, input that is checked but not encrypted, so Decrypt must present the same pair.

KMS calls are CloudTrail , recorded by default, and the encryption context appears in each one in plain text. Policies can test it with kms:EncryptionContext:aws:s3:arn, and an auditor can see exactly which object was read. The price of that visibility is a rule: nothing secret goes into an encryption context.

STEP 05 / 05

ONE MORE CALL TO READ IT BACK

CLOUDTRAIL · ledger-prod · us-east-1
  1. #1CreateKeykey-adminok2026-11-05 16:00:00 · us-east-1
  2. #2CreateAliaskey-adminok2026-11-05 16:00:04 · us-east-1
  3. #3GenerateDataKeyinvoice-app via S3ok2026-11-06 09:00:00 · us-east-1
  4. #4Decryptinvoice-app via S3ok2026-11-06 09:00:02 · us-east-1
Σ KMS calls so far4
Fig 1.5Rows #3 and #4 are this chapter’s two calls: one to encrypt the invoice, one to read it back.

At 09:00:02 the mailer reads the invoice back with GetObject to attach it to the customer’s email. S3 sends the stored encrypted data key and the encryption context to Decrypt, call #4, gets the plaintext data key back, and decrypts the PDF before returning it.

Without S3 Bucket Keys, every read of an SSE-KMS object is a KMS request on the bill. One million reads cost $3.00 in Decrypt calls (1,000,000 ÷ 10,000 × $0.03), before the free tier. Σ KMS calls is now 4.

WHAT YOU NOW KNOW

  • KMS encrypts at most 4,096 bytes, so S3 asks it for a 32-byte data key and encrypts the 188,416-byte file itself.
  • The encrypted data key is stored with the object and names its KMS key; the encryption context names the object.
  • Writing the invoice took one GenerateDataKey and reading it took one Decrypt: 4 calls so far.
deep dive: the AWS Encryption SDK, data key caching and ReEncrypt

The AWS Encryption SDK runs the same envelope pattern in your own process, with a portable message format that bundles the encrypted data keys and the ciphertext. KMS’s own Decrypt cannot read that format; the SDK unpacks it first. Its data key caching reuses one data key for many messages, trading fewer KMS calls for a plaintext key that lives longer in memory.

GenerateDataKeyWithoutPlaintext returns only the encrypted copy, for services that store a key now and decrypt it later, as EBS does in chapter 03. ReEncrypt moves a ciphertext from one KMS key to another without the plaintext ever leaving KMS. Asymmetric KMS keys (RSA and elliptic curve) sign, verify and encrypt with a public key you can download; HMAC keys compute message authentication codes.

ON THE EXAM

  • “encrypt a 2 GB file with KMS” → GenerateDataKey and envelope encryption, never Encrypt
  • “KMS ThrottlingException at high S3 request rates” → S3 Bucket Keys, or data key caching (chapter 03)
  • “allow decryption of one object only” → a kms:EncryptionContext:aws:s3:arn condition
  • “what CloudTrail shows for an SSE-KMS read” → a Decrypt event with the reader as userIdentity and S3 as invokedBy

The key policy has the last word

At 14:10 a developer with read access to every bucket in the account asks for INV-0042.pdf and gets a 403. S3 allowed the request, and KMS refused it. A KMS key decides who may use it through its own policy, separately from S3’s permissions. The steps below read that policy statement by statement, and the refused call takes Σ from 4 to 5 KMS calls.

STEP 01 / 05

THE ONE RESOURCE THAT MUST TRUST YOUR ACCOUNT

statement 1 · Enable IAM User Permissions
Principal
arn:aws:iam::111122223333:root
Action
kms:*
means
IAM policies in this account may grant use of the key
statement 2 · key administrators
Principal
role/key-admin
Action
Create*, Describe*, Put*, ScheduleKeyDeletion, …
statement 3 · key users
Principal
role/invoice-app
Action
Decrypt, GenerateDataKey
a principal named in no statement, directly or through IAM, is refused
Fig 2.1The accent statement makes IAM policies count. Without it, only principals named in the key policy can use the key.

Every KMS key carries exactly one , a resource policy attached to the key itself. No principal can use invoices-2026 unless that policy allows it, either by naming the principal or by handing the decision to IAM. S3 buckets work differently: in the bucket’s own account, an IAM policy alone can open a bucket, but never a key.

The handover is one statement whose principal is the account itself, arn:aws:iam::111122223333:root. The console names it “Enable IAM User Permissions”. With it, IAM policies in the account count. Without it, a role with AdministratorAccess is refused, and a policy that names nobody able to manage the key can only be repaired by AWS Support.

THE FIRST TWO STATEMENTS OF THE INVOICES-2026 KEY POLICY
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "Enable IAM User Permissions",
      "Effect": "Allow",
      "Principal": { "AWS": "arn:aws:iam::111122223333:root" },
      "Action": "kms:*",
      "Resource": "*"
    },
    {
      "Sid": "Key administrators",
      "Effect": "Allow",
      "Principal": { "AWS": "arn:aws:iam::111122223333:role/key-admin" },
      "Action": ["kms:Create*", "kms:Describe*", "kms:Enable*", "kms:Put*",
                 "kms:Disable*", "kms:ScheduleKeyDeletion", "kms:CancelKeyDeletion"],
      "Resource": "*"
    }
  ]
}
STEP 02 / 05

READ-ONLY, EXCEPT FOR THE BYTES

  1. 1 · S3s3:GetObjectReadOnlyAccess grants s3:Get* on every bucket✓ passes
  2. 2 · KMSkms:DecryptReadOnlyAccess grants only kms:Describe*, Get*, List*✗ AccessDenied
  1. #5Decryptdev-readonly via S3AccessDeniedException2026-11-06 14:10:17 · us-east-1
Σ KMS calls so far5
Fig 2.2Two checks, two answers: the S3 check in the first row passes, and the accent KMS check in the second row refuses.

At 14:10 dev-readonly asks for INV-0042.pdf. The role has the AWS managed ReadOnlyAccess policy, which allows s3:Get* on every bucket and, for KMS, only kms:Describe*, kms:Get* and kms:List*. S3 checks s3:GetObject first, and it passes.

Then S3 calls Decrypt as dev-readonly, and KMS finds nothing in the key policy or the role that allows it. KMS returns AccessDeniedException, S3 returns a 403 to the developer, and CloudTrail records the refused call as #5. Σ moves from 4 to 5.

STEP 03 / 05

ADMINISTRATORS WHO CANNOT DECRYPT

key-admin
  • Decrypt· not allowed
  • GenerateDataKey· not allowed
  • PutKeyPolicy✓ allowed
  • ScheduleKeyDeletion✓ allowed
PutKeyPolicy can add a Decrypt statement for key-admin
invoice-app
  • Decrypt✓ allowed
  • GenerateDataKey✓ allowed
  • PutKeyPolicy· not allowed
  • ScheduleKeyDeletion· not allowed
dev-readonly
  • Decrypt· not allowed
  • GenerateDataKey· not allowed
  • PutKeyPolicy· not allowed
  • ScheduleKeyDeletion· not allowed
Fig 2.3Read the key-admin card: no Decrypt cell is ticked, but the accent PutKeyPolicy cell can add one.

The console’s default key policy splits people into two statements. Key administrators get the management actions, such as kms:Create*, kms:Put*, kms:Disable* and kms:ScheduleKeyDeletion, and no kms:Decrypt. Key users get Encrypt, Decrypt, ReEncrypt*, GenerateDataKey* and DescribeKey.

The split has one weak point. kms:PutKeyPolicy replaces the whole policy, so an administrator who holds it can add a statement giving their own role Decrypt. That makes it the permission to alert on. Every PutKeyPolicy is a CloudTrail event, and chapter 04 adds one.

STEP 04 / 05

DECRYPT ONLY WHEN S3 ASKS

invoice-app → S3 → DecryptGetObject on ledger-invoices; S3 calls KMS for the role✓ allowed
invoice-app → Decryptcopied credentials and an encrypted data key, called directly✗ refused
statement · invoice-app
Action
kms:Decrypt, kms:GenerateDataKey
ViaService
s3.us-east-1.amazonaws.com
EncryptionContext
aws:s3:arn = arn:aws:s3:::ledger-invoices/*
both conditions must match, or the statement allows nothing
Fig 2.4Same role, same key, two requests: only the one arriving through S3 matches the accent condition.

invoice-app’s statement in the key policy carries two conditions. must equal s3.us-east-1.amazonaws.com, so the request has to come from S3 acting for the role. kms:EncryptionContext:aws:s3:arn must match arn:aws:s3:::ledger-invoices/*, so the object has to be in this bucket.

Suppose an attacker copies the app’s credentials and one encrypted data key. A direct Decrypt call from their laptop matches neither condition and is refused. The same credentials can still read invoices through S3, where the bucket’s own policy and logs apply.

THE INVOICE-APP STATEMENT, WITH BOTH CONDITIONS
{
  "Sid": "invoice-app, through S3 only",
  "Effect": "Allow",
  "Principal": { "AWS": "arn:aws:iam::111122223333:role/invoice-app" },
  "Action": ["kms:Decrypt", "kms:GenerateDataKey"],
  "Resource": "*",
  "Condition": {
    "StringEquals": {
      "kms:ViaService": "s3.us-east-1.amazonaws.com"
    },
    "StringLike": {
      "kms:EncryptionContext:aws:s3:arn": "arn:aws:s3:::ledger-invoices/*"
    }
  }
}
STEP 05 / 05

PERMISSIONS THAT AREN’T IN ANY POLICY

key policy
attached to
the key, exactly one
read with
GetKeyPolicy
IAM policies
attached to
roles and users
count only if
the key policy trusts the account
grants
attached to
the key, up to 50,000
read with
ListGrants, never GetKeyPolicy
made by
EBS, RDS and other services
allowed?
yes if any source allows and none denies
Fig 2.5Three sources feed one decision. The accent card, grants, is the one a policy review never shows you.

A gives one principal permission to use one key for a listed set of operations, without editing the key policy. AWS services create grants as they work: EBS when it attaches an encrypted volume, RDS when it starts an encrypted instance. Chapter 03 shows both.

One key holds up to 50,000 grants, the service-made ones included. They appear in ListGrants, never in GetKeyPolicy, and they end with RetireGrant or RevokeGrant. So the full answer to “who can decrypt invoices-2026?” comes from three places: the key policy, the IAM policies in the account, and the grant list.

THE GRANTS ON INVOICES-2026
aws kms list-grants \
  --key-id mrk-1234abcd12ab34cd56ef1234567890ab \
  --query 'Grants[].[GranteePrincipal,Operations]' \
  --output text

# Every grant on the key, including the ones AWS services created.
# None of these appear in:
aws kms get-key-policy \
  --key-id mrk-1234abcd12ab34cd56ef1234567890ab \
  --policy-name default

WHAT YOU NOW KNOW

  • A KMS key’s own policy decides who can use it; IAM policies count only when the key policy delegates to the account.
  • dev-readonly passed S3’s check and failed KMS’s: one refused Decrypt, 5 calls so far.
  • Conditions such as kms:ViaService and the encryption context narrow access, and grants extend it outside any policy document.
deep dive: policy lockout, grant tokens, and auditing who can decrypt

PutKeyPolicy runs a lockout safety check: it refuses a policy that would stop the caller from changing the policy again, unless the request sets BypassPolicyLockoutSafetyCheck. Two more condition keys come up often. kms:CallerAccount limits a statement to callers from one account, and kms:GrantIsForAWSResource lets a grant through only when an AWS service such as EBS creates it, which blocks hand-made grants.

A new grant can take a few seconds to take effect everywhere. A caller that needs it at once passes the grant token returned by CreateGrant. IAM Access Analyzer reads key policies too, and flags any that grant access to principals outside your organization.

A bucket policy and a key policy are evaluated separately, and a cross-account read needs both, as chapter 04 shows. For conditions on the bucket side, such as accepting requests from one VPC endpoint only with aws:SourceVpce, see the AWS VPC tutorial’s chapter 04.

ON THE EXAM

  • “an administrator gets AccessDenied on a KMS key” → the key policy names neither the account root nor the administrator
  • “stop key administrators from reading the data” → separate admin and user statements, and an alarm on PutKeyPolicy
  • “the key may only be used by S3” → a kms:ViaService condition
  • “an EBS volume with a customer managed key fails to attach” → the key policy must allow kms:CreateGrant for the attaching principal

Four services, four ways to call KMS

The invoice also exists as a database row and as a temporary file on a disk, and between them they add 0 KMS calls. Block storage and databases call KMS when a volume is attached or an instance starts, then encrypt every write with a key already in memory. The steps below cover EBS, RDS, DynamoDB, Secrets Manager and S3 Bucket Keys, and Σ holds at 5.

STEP 01 / 05

A DISK THAT CALLS KMS WHEN IT IS ATTACHED

3 KMS calls
PDF render writes its temp file
0 KMS calls
  1. createGenerateDataKeyWithoutPlaintext
  2. attachCreateGrant
  3. attachDecrypt
after attach, the plaintext volume key stays in host memory, outside the instance
Fig 3.1The three accent calls happen at create and attach time. The long run of writes after them makes none.

The PDF renderer writes a temporary copy of INV-0042 to an encrypted EBS volume. EBS called KMS when the volume was created, with GenerateDataKeyWithoutPlaintext, which returns only the encrypted copy of a new data key. It called again at attach time: CreateGrant for the host, then Decrypt to recover the volume’s data key.

From then on the plaintext volume key stays in host memory, outside the instance, and every block the renderer writes is encrypted there. The temporary file costs no KMS calls. Encryption by default is an account setting in each Region, off until you turn it on.

STEP 02 / 05

THE DATABASE KEY IS CHOSEN ONCE

ledger-dbencrypted under alias/ledger-db, key fixed
snapshotsame key as the instance
copy, new keycopy-db-snapshot --kms-key-id
ledger-db-v2new instance, new endpoint
Fig 3.2Follow the four boxes: the accent box, the snapshot copy, is the only step where a different key can be chosen.

ledger-db, the RDS Postgres database that holds INV-0042’s row, was created encrypted under a second key, alias/ledger-db. Like EBS, RDS calls KMS when the instance starts, not on each insert, so the invoice’s row added 0 calls.

The key choice is final. RDS can’t encrypt an existing unencrypted instance, can’t move an instance to another key, and can’t make an encrypted read replica of an unencrypted instance. The one route is a snapshot, a copy of that snapshot under the new key, and a restore, which builds a new instance with a new endpoint.

RE-KEYING LEDGER-DB THROUGH A SNAPSHOT COPY
aws rds copy-db-snapshot \
  --source-db-snapshot-identifier ledger-db-2026-11-06 \
  --target-db-snapshot-identifier ledger-db-rekeyed \
  --kms-key-id alias/ledger-db-2027      # the new key is chosen here, and only here

aws rds restore-db-instance-from-db-snapshot \
  --db-snapshot-identifier ledger-db-rekeyed \
  --db-instance-identifier ledger-db-v2

# ledger-db-v2 is a new instance with a new endpoint:
# the application's connection string has to change.
STEP 03 / 05

THE TABLE THAT SWITCHES KEYS WHILE SERVING TRAFFIC

S3
key options
SSE-S3, aws/s3, customer managed
change later
new writes only
DynamoDB
key options
AWS owned (default), aws/dynamodb, customer managed
change later
yes, live (UpdateTable)
EBS
key options
aws/ebs, customer managed
change later
no, copy the snapshot
RDS
key options
aws/rds, customer managed
change later
no, restore a copy
Fig 3.3Read the “change later” rows: the accent one is DynamoDB’s yes beside RDS’s no.

The invoice index lives in a DynamoDB table, which is encrypted at rest with no way to turn that off. It offers three keys: an AWS owned key (the default, free and invisible), the AWS managed key aws/dynamodb, or a customer managed key.

An UpdateTable call switches between them while the table keeps serving reads and writes. That is the opposite of RDS in the previous step. An exam question asking to “change the key with no downtime” has a one-call answer for DynamoDB and a migration for RDS.

STEP 04 / 05

THE PASSWORD IS ENCRYPTED THE SAME WAY

invoice-appneeds the ledger-db password
Secrets Manager$0.40 a secret a month
KMSDecrypt under the secret’s key
secret under aws/secretsmanager
readable from another account
no, the key policy is fixed
secret under a customer managed key
readable from another account
yes, with the key policy and a resource policy
Fig 3.4Follow the GetSecretValue arrow: Secrets Manager makes its own accent Decrypt call, under whatever key the secret names.

The app’s database password sits in Secrets Manager, envelope-encrypted under a KMS key like the invoice. Each GetSecretValue makes Secrets Manager call Decrypt, so an app that fetches the secret on every request also calls KMS on every request. Client-side caching cuts that to one fetch per cache period.

A secret costs $0.40 a month plus $0.05 per 10,000 API calls. Parameter Store SecureString parameters on the standard tier are free up to 4 KB, and they encrypt through KMS too. In both, the key decides sharing: a secret under aws/secretsmanager can’t be read from another account, because nobody can add that account to an AWS managed key’s policy.

STEP 05 / 05

ONE KEY FOR A WHOLE BUCKET, FOR A WHILE

Bucket Key off2,000,000 KMS calls a day
encryption context in CloudTrailaws:s3:arn …:ledger-invoices/2026/11/INV-0042.pdf
Bucket Key on20,000 KMS calls a day
encryption context in CloudTrailaws:s3:arn …:ledger-invoices
Fig 3.5Compare the two encryption-context boxes under the bars: with the accent Bucket Key, the object’s name is gone from the log.

With an , S3 asks KMS for a short-lived bucket-level key and derives each object’s data key from it, instead of calling KMS for every object. AWS puts the saving at up to 99% of KMS requests. At 2,000,000 reads a day, that is 2,000,000 Decrypt calls ($6.00) against about 20,000 ($0.06).

The cost is visibility. The encryption context becomes the bucket ARN, so every KMS event names ledger-invoices instead of the object, and key-policy conditions written on object ARNs stop matching. The bucket keeps Bucket Keys off to keep a per-object trail, so Σ still holds at 5.

LEDGER-INVOICES DEFAULT ENCRYPTION, BUCKET KEY OFF
aws s3api put-bucket-encryption \
  --bucket ledger-invoices \
  --server-side-encryption-configuration '{
    "Rules": [{
      "ApplyServerSideEncryptionByDefault": {
        "SSEAlgorithm": "aws:kms",
        "KMSMasterKeyID": "arn:aws:kms:us-east-1:111122223333:key/mrk-1234abcd12ab34cd56ef1234567890ab"
      },
      "BucketKeyEnabled": false
    }]
  }'
# true would cut KMS requests by up to 99%, and CloudTrail would log
# the bucket ARN instead of each object's ARN.

WHAT YOU NOW KNOW

  • EBS and RDS call KMS when a volume attaches or an instance starts, so the row and temp file add 0 calls; Σ stays at 5.
  • RDS fixes its key at creation, DynamoDB switches keys live, and AWS managed keys keep secrets inside one account.
  • S3 Bucket Keys cut KMS requests by up to 99%, but CloudTrail then logs the bucket instead of the object.
deep dive: Aurora, EFS, CloudWatch Logs, ACM and data in transit

An Aurora or RDS read replica in the same Region shares its source’s key; a cross-Region replica needs a key in the destination Region. EFS fixes encryption at rest when the file system is created, like RDS. CloudWatch Logs can encrypt a log group with a customer managed key, but only if the key policy allows logs.us-east-1.amazonaws.com to use it.

Every one of these service calls counts against the account’s KMS request quota, which is shared per Region: 100,000 symmetric requests per second in us-east-1, 10,000 in the baseline Regions. A burst of SSE-KMS reads can therefore throttle an unrelated Secrets Manager call in the same account.

Data in transit, briefly. ACM issues public TLS certificates at no charge and keeps their private keys encrypted. ACM Private CA issues internal certificates. A bucket policy that denies requests where aws:SecureTransport is false refuses plain HTTP.

ON THE EXAM

  • “encrypt an existing unencrypted RDS database” → snapshot, copy the snapshot with encryption on, restore
  • “encrypt the existing EBS volumes” → snapshot, encrypted copy, new volume, plus encryption by default in every Region
  • “share a secret with another account” → a customer managed key plus a resource policy on the secret
  • “reduce SSE-KMS cost or KMS throttling” → S3 Bucket Keys

Another account, another Region, the same key

The external auditor works in account 444455556666, and nothing in that account has permission to use invoices-2026. Reading INV-0042 from there takes a change to the key policy and a matching IAM policy on the auditor’s side. A second copy, in us-west-2, takes a key in that Region. This chapter adds 6 calls, one of which the key owner never sees, and takes Σ from 5 to 11.

STEP 01 / 05

THE KEY THAT CAN NEVER LEAVE ITS ACCOUNT

snap-0ledger2026 · aws/ebs
key
aws/ebs, AWS managed
key policy
written by EBS, not editable
share with 444455556666
✗ not possible
snap-0ledgercopy · customer managed
key
alias/snapshots-shared
key policy
yours to edit
share with 444455556666
✓ once the key policy names it
Fig 4.1Both snapshots hold the same data. Only the accent copy, under a customer managed key, has a policy another account can join.

An AWS managed key’s policy is written by the service that created it, and nobody can edit it. Cross-account access needs exactly that edit: a statement naming the other account. So aws/s3, aws/ebs, aws/rds and aws/secretsmanager can never be used from outside the account that holds them.

The exam’s favourite case is an EBS snapshot. A snapshot encrypted with aws/ebs can’t be shared at all. The fix is a copy in the same Region under a customer managed key: share that key with the other account, then share the copy. This is why invoices-2026 was customer managed from its first day.

STEP 02 / 05

REFUSED IN A LOG THE OWNER NEVER SEES

111122223333 · KEY OWNER’S TRAIL
no record of call #6
444455556666 · CALLER’S TRAIL
  1. #6Decryptauditor @4444… via S3AccessDeniedException2027-03-31 10:12:09 · us-east-1
Fig 4.2Look at which trail holds the accent event: the auditor’s account, not the account that owns the key.

On 2027-03-31 at 10:12 the auditor role in account 444455556666 asks S3 for INV-0042. The bucket policy names that account, so S3’s own check passes. S3 then calls Decrypt as the auditor’s role, call #6, and KMS refuses it: the key policy has no statement for 444455556666.

KMS logs a refused cross-account request only in the caller’s account. The auditor’s CloudTrail holds call #6, and ledger-prod’s trail has no record that anyone outside the account tried. To see refused attempts on its key, the key owner has to ask the other account for its logs.

STEP 03 / 05

BOTH ACCOUNTS HAVE TO SAY YES

key policy · 111122223333
Effect
Allow
Principal
arn:aws:iam::4444…:root
Action
kms:Decrypt
Condition
kms:ViaService = s3.us-east-1…
IAM policy on role/auditor · 444455556666
Effect
Allow
Action
kms:Decrypt
Resource
arn:aws:kms:us-east-1:1111…:key/mrk-1234…
a key ARN: alias/invoices-2026 doesn’t exist in this account
GetObject INV-0042.pdfauditor @444455556666, via S3✓ allowed
  1. #6Decryptauditor @4444… via S3AccessDeniedException2027-03-31 10:12:09 · us-east-1
  2. #7PutKeyPolicykey-adminok2027-03-31 10:30:51 · us-east-1
  3. #8Decryptauditor @4444… via S3ok2027-03-31 10:41:33 · us-east-1
Fig 4.3Two policy cards in two accounts. The request passes only because the accent line in each one allows it.

At 10:30 key-admin calls PutKeyPolicy, call #7. The new statement names arn:aws:iam::444455556666:root, allows kms:Decrypt, and keeps the auditor to S3 with kms:ViaService. Naming the account’s root hands the final decision to that account’s own IAM policies.

So the auditor’s account must also give its role kms:Decrypt on the key, and it has to name the full key ARN. An alias is looked up in the caller’s own account, where alias/invoices-2026 doesn’t exist. At 10:41 the read succeeds, call #8, and this time it is logged in both accounts.

CALL #7: THE STATEMENT ADDED TO THE KEY POLICY
{
  "Sid": "Auditor account, through S3 only",
  "Effect": "Allow",
  "Principal": { "AWS": "arn:aws:iam::444455556666:root" },
  "Action": "kms:Decrypt",
  "Resource": "*",
  "Condition": {
    "StringEquals": {
      "kms:ViaService": "s3.us-east-1.amazonaws.com"
    }
  }
}
STEP 04 / 05

A REPLICA RE-ENCRYPTED ON ARRIVAL

US-EAST-1ledger-invoices#9 Decrypt ·
US-WEST-2ledger-invoices-dr#10 Encrypt ·
INV-0042 lands in ledger-invoices, and the rule opts in to SSE-KMS objects
2026/11/INV-0042.pdf
x-amz-server-side-encryptionaws:kms
keyarn:aws:kms:us-east-1:…:key/mrk-1234…
encrypted data keyunder the us-east-1 key
ciphertext · 188,416 bytes
Fig 4.4Watch the key field in the object’s header: it is struck out and rewritten as the object crosses into us-west-2.

At 09:00:41 on 2026-11-06, the day INV-0042 was written, S3 replication copied it to ledger-invoices-dr in us-west-2. Replication skips SSE-KMS objects unless the rule opts in with SseKmsEncryptedObjects and names a destination key in ReplicaKmsKeyID, which must be a key in the destination Region.

The s3-replication role needs kms:Decrypt on the source key and kms:Encrypt on the destination key. It calls Decrypt in us-east-1, call #9, and Encrypt in us-west-2, call #10. S3 doesn’t test the key when the rule is saved: a rule naming the wrong key still returns 200 OK, and every replication then fails.

STEP 05 / 05

THE SAME KEY ID IN TWO REGIONS

SHARED BY BOTH KEYS
KeyId
mrk-1234abcd12ab34cd56ef1234567890ab
key material
the same versions in both Regions
rotation
set on the primary, applied to both
us-east-1PRIMARY
key policy
its own
grants
its own
aliases
alias/invoices-2026
us-west-2REPLICA
key policy
its own, the default at creation
grants
its own
aliases
none until one is created
Σ KMS calls so far11
Fig 4.5The shared rows are drawn once across both keys. The accent rows, the key policies, are separate in each Region.

invoices-2026 is a . On 2026-11-05 key-admin called ReplicateKey, call #11, which created a replica in us-west-2 with the same key ID and the same key material. The replica is still a separate key, with its own key policy, grants and aliases, billed as a second key.

S3 replication didn’t need the shared material, because S3 decrypts and re-encrypts every replica anyway. A multi-Region key matters when ciphertext crosses Regions without being re-encrypted, such as items encrypted client-side and copied by a DynamoDB global table. The replica Region then decrypts locally, without calling us-east-1.

That makes 6 calls in this chapter, and Σ stands at 11.

CALL #11: THE REPLICA IN US-WEST-2
aws kms replicate-key \
  --key-id mrk-1234abcd12ab34cd56ef1234567890ab \
  --replica-region us-west-2 \
  --description "2026 invoices, DR"

# The replica has the same key ID and key material:
#   "KeyId": "mrk-1234abcd12ab34cd56ef1234567890ab",
#   "Arn": "arn:aws:kms:us-west-2:111122223333:key/mrk-1234abcd12ab34cd56ef1234567890ab"
# but its own key policy (pass --policy, or it gets the default).

WHAT YOU NOW KNOW

  • AWS managed keys can’t be shared, so cross-account access always starts with a customer managed key.
  • A cross-account read needs the key policy and the caller’s IAM policy, naming the key ARN; the refusal was logged only by the auditor.
  • Replication re-encrypts under a key in the destination Region; a multi-Region key shares material but not policies. 11 calls so far.
deep dive: sharing EBS and RDS snapshots, and multi-Region keys with DynamoDB global tables

An RDS snapshot under a customer managed key is shared the same way as an EBS one: share the snapshot, add the other account to the key policy, and let the receiving account copy the snapshot under a key of its own. A copy to another Region always needs a key in the destination Region, because KMS keys never leave the Region they were created in, apart from multi-Region replicas.

A single-Region key can’t be converted into a multi-Region key; MultiRegion is fixed at creation. UpdatePrimaryRegion promotes a replica to primary. Each replica is a copy of the key material in another Region, which some data-residency rules count as moving the key. To share with a whole organization instead of one account, a key policy can test aws:PrincipalOrgID.

ON THE EXAM

  • “share an encrypted AMI or snapshot with another account” → re-encrypt it under a customer managed key and share the key
  • “a cross-account Decrypt fails” → check the key policy and the caller’s IAM policy, and use the key ARN
  • “replicate SSE-KMS objects” → opt in with SseKmsEncryptedObjects and set ReplicaKmsKeyID
  • “decrypt the same ciphertext in two Regions without calling the first Region” → a multi-Region key

A rotated key still decrypts the old invoice

What should happen to INV-0042 on 5 November 2027, when invoices-2026 rotates for the first time? Nothing, and that is the correct answer. Rotation gives a KMS key new key material for future encryptions and keeps every old version for decryption, so the 2026 ciphertext stays exactly as it was written. This chapter adds 3 calls and takes Σ from 11 to 14.

STEP 01 / 04

ROTATION IS ONE FLAG AND ONE NUMBER

90
365
1,095
2,560
1 yr2 yr3 yr4 yr5 yr6 yr7 yr
rotation period, days (ticks under the axis are years)
365 days
invoices-2026, and every AWS managed key since May 2022
1,095 days
AWS managed keys before May 2022
90–2,560 days
the range a customer managed key accepts
2,560 days
7.01 years: one rotation in INV-0042’s whole retention
Fig 5.1The accent marker is invoices-2026’s 365 days. The ruler’s far end, 2,560 days, would rotate once in seven years.

is one API call. On 2026-11-05, minutes after creating the key, key-admin called EnableKeyRotation with RotationPeriodInDays set to 365, call #12. Every 365 days KMS now generates new for invoices-2026 and makes it the version used for new encryptions.

Since 2024-04-12 the period can be anything from 90 to 2,560 days, with 365 as the default, and RotateKeyOnDemand rotates at once. AWS managed keys rotate every year, a schedule AWS shortened from about 1,095 days in May 2022.

Automatic rotation isn’t available for asymmetric keys, HMAC keys or keys in custom key stores. Keys with imported key material can’t rotate automatically either; since 2025-06-06 they can rotate on demand.

CALL #12, AND WHAT KMS REPORTS AFTERWARDS
aws kms enable-key-rotation \
  --key-id mrk-1234abcd12ab34cd56ef1234567890ab \
  --rotation-period-in-days 365        # 90 to 2560

aws kms get-key-rotation-status \
  --key-id mrk-1234abcd12ab34cd56ef1234567890ab
#   "KeyRotationEnabled": true,
#   "RotationPeriodInDays": 365,
#   "NextRotationDate": "2027-11-05T16:06:40+00:00"

aws kms list-key-rotations \
  --key-id mrk-1234abcd12ab34cd56ef1234567890ab
#   after 2027-11-05: one entry, "RotationType": "AUTOMATIC"
STEP 02 / 04

NEW KEY MATERIAL, SAME KEY ID

alias/invoices-2026
KeyId
mrk-1234… · unchanged
alias and key policy
unchanged
material v1
2026-11-05 · kept for decryption
material v2
2027-11-05 · current
RotateKey · eventType AwsServiceEvent · rotationType AUTOMATIC
ENCRYPTED DATA KEY NAMES
INV-0042.pdfwritten 2026-11-06key material v1
INV-2028-0007.pdfwritten 2028-01-04key material v2
Fig 5.2Read the material rows: new invoices use the accent version, v2, while INV-0042’s encrypted data key still names v1.

On 2027-11-05 KMS rotates invoices-2026 by itself, call #13. CloudTrail records a RotateKey event with eventType AwsServiceEvent and rotationType AUTOMATIC: there is no caller, because nobody asked. New key material becomes the current version, used for every encryption from then on.

Nothing else about the key changes. The key ID, ARN, alias, key policy and grants stay the same, so no application or policy needs an update. The 2026 key material stays inside KMS, and so will each later version, until the key itself is deleted. Not one object is re-encrypted.

STEP 03 / 04

THE 2028 DOWNLOAD THAT NEEDED 2026’S KEY MATERIAL

#12
#13
#14
20272028
  1. #12EnableKeyRotationkey-adminok2026-11-05 16:06:40 · us-east-1
  2. #13RotateKeyKMSok2027-11-05 · us-east-1
  3. #14Decryptinvoice-app via S3ok2028-02-14 19:22:05 · us-east-1
Σ KMS calls so far14
Fig 5.3Rows #12 to #14 span 15 months: one setting, one automatic rotation, and a read that used the first key material.

On 2028-02-14 the customer downloads INV-0042 again from the billing portal. S3 sends KMS the same encrypted data key it stored in 2026, call #14. That ciphertext records which version of the key material encrypted it, and KMS decrypts it with that version: the 2026 one.

Neither S3 nor invoice-app knows a rotation happened, and neither has to. A caller never picks a key version; KMS reads it from the ciphertext. Rows #12 to #14 are this chapter’s 3 calls, and they bring Σ to 14.

STEP 04 / 04

WHAT ROTATION DOES NOT PROTECT

one version of the key material encrypts too much data
rotation helps?yes
what doesnew data goes under new material
a plaintext data key leaked
rotation helps?no
what doesre-encrypt that object under a new data key
credentials with kms:Decrypt leaked
rotation helps?no
what doesrevoke them, re-encrypt under a new key, disable the old one
Fig 5.4Read the “rotation helps?” line of each card: only the first risk, too much data under one version, gets the accent yes.

Rotation limits how much data one version of the key material encrypts. It does nothing for two other risks. A plaintext data key that leaked decrypts its one object forever, whatever happens to the KMS key. Leaked credentials with kms:Decrypt decrypt data under every version, old and new.

After a suspected compromise, create a new key and re-encrypt the data under it: ReEncrypt for KMS ciphertext, an S3 copy in place for objects. Then point the alias at the new key with UpdateAlias and disable the old one.

Rotation has a small price. The first and second rotations each add $1 a month to the key, and later rotations add nothing, so a rotating key tops out at $3 a month.

WHAT YOU NOW KNOW

  • Rotation adds new key material under the same key ID; INV-0042 still decrypts with its 2026 material.
  • Periods run 90–2,560 days; AWS managed keys rotate yearly; imported key material rotates only on demand.
  • Rotation does nothing for a leaked data key or leaked credentials; that needs a new key and re-encryption. 14 calls so far.
deep dive: manual rotation with aliases, and rotation pricing

Keys that can’t rotate automatically still rotate by hand: create a new key, point the alias at it with UpdateAlias, and keep the old key enabled so existing ciphertext still decrypts. Code that names the alias picks up the new key at once; code that names the old key ID does not, which is the usual reason to call keys by alias. ListKeyRotations shows a key’s rotation history, and on-demand rotations have their own per-key quota.

Compliance frameworks still ask for yearly rotation even though KMS keeps every old version, because it bounds how much data any one version of the key material protects. The price is $1 a month for each of the first two rotations, prorated hourly, and nothing after that.

ON THE EXAM

  • “rotate yearly with the least effort” → automatic rotation on a customer managed key
  • “rotate a key with imported key material” → RotateKeyOnDemand, or a new key plus an alias swap
  • “does rotation require re-encrypting data?” → no
  • “a data key was exposed” → re-encrypt the affected data under a new key; rotation alone is the wrong answer

Where the key material actually lives

Every KMS key has an Origin field, and invoices-2026’s says AWS_KMS: KMS generated its key material and keeps it in KMS’s own HSMs. Three other values exist, and each moves part of the job from AWS to you: keeping a copy of the material, running the HSMs, or running the service that holds the key. One DescribeKey call takes Σ from 14 to 15 KMS calls.

STEP 01 / 05

THE FIELD AN AUDITOR ASKS ABOUT

describe-key · alias/invoices-2026
KeyId
mrk-1234abcd12ab34cd56ef1234567890ab
KeyManager
CUSTOMER
Origin
AWS_KMS
KeyState
Enabled
MultiRegionKeyType
PRIMARY
call #15 · compliance · 2029-06-01
EVERY VALUE ORIGIN CAN TAKE
  • AWS_KMSthis key: KMS generated the material
  • EXTERNALstep 2: material you imported
  • AWS_CLOUDHSMstep 3: material in your CloudHSM cluster
  • EXTERNAL_KEY_STOREstep 4: material outside AWS
fixed at CreateKey, never changed afterwards
Fig 6.1The accent row is Origin. The second panel lists all four values it can take, each with the step that covers it.

On 2029-06-01 the compliance role calls DescribeKey on invoices-2026, call #15. The answer carries the four fields an auditor asks about: Origin AWS_KMS, KeyManager CUSTOMER, KeyState Enabled and MultiRegionKeyType PRIMARY. Origin says where the key material was made and where it lives.

Origin takes four values. AWS_KMS means KMS generated the material in its own HSMs; EXTERNAL means you imported it; AWS_CLOUDHSM means it lives in a CloudHSM cluster you own; EXTERNAL_KEY_STORE means it never enters AWS. CreateKey sets the value for good, so a different origin means a new key and re-encrypted data. Σ goes from 14 to 15.

CALL #15: THE KEY’S ORIGIN, AS DESCRIBE-KEY REPORTS IT
aws kms describe-key \
  --key-id alias/invoices-2026 \
  --query 'KeyMetadata.[Origin,KeyManager,KeyState,MultiRegionConfiguration.MultiRegionKeyType]' \
  --output text

# AWS_KMS    CUSTOMER    Enabled    PRIMARY
# Origin is fixed at creation. The other values are
# EXTERNAL, AWS_CLOUDHSM and EXTERNAL_KEY_STORE.
STEP 02 / 05

KEY MATERIAL YOU BROUGHT, AND MUST KEEP

CreateKeyOrigin EXTERNAL · no material yet
GetParametersForImportwrapping public key + import token
wrap your key256-bit material, kept on your side too
ImportKeyMaterialoptional expiry (ValidTo)
DeleteImportedKeyMaterial · takes effect at onceKMS regenerating it · impossible
Fig 6.2Follow the four boxes in order. The accent box is the copy that stays on your side, and KMS cannot recreate it.

A key with origin EXTERNAL starts empty. CreateKey makes its key ID and policy, GetParametersForImport returns a wrapping public key and an import token, and you encrypt your own 256-bit key material with that public key on your side. ImportKeyMaterial hands the wrapped material to KMS, which unwraps it inside its HSMs. This is , often called BYOK.

Two controls come with it. You can set an expiry date, after which KMS deletes the material, and DeleteImportedKeyMaterial removes it at once, with no waiting period. KMS can’t regenerate imported material. If its copy is deleted or expires and you no longer have yours, everything encrypted under the key is unreadable for good.

STEP 03 / 05

HSMS THAT ONLY YOUR TEAM CAN LOG IN TO

KMS KEY · ORIGIN AWS_KMS
invoices-2026$1.00/month
one customer managed key
HSMs you run$0.00/month
per month$1.00
KMS KEY IN A CLOUDHSM KEY STORE
the KMS key$1.00/month
one customer managed key
2 HSMs, hsm2m.medium$2,336.00/month
2 × 730 h × $1.60 an hour
per month$2,337.00
Fig 6.3Both receipts protect the same invoice with the same S3 call. The accent line is the HSM-hours a custom key store adds.

A key with origin AWS_CLOUDHSM lives in a . KMS keeps the key ID, the key policy and the API, but the key material is generated and used inside an cluster your team owns. The cluster is single-tenant, and only users you create can log in to it. AWS can’t recover its keys if you lose those credentials.

KMS connects a key store only to a cluster with at least two active HSMs in different Availability Zones. At the us-east-1 list price for hsm2m.medium in November 2026, $1.60 an hour, two HSMs cost 2 × 730 × $1.60 = $2,336 a month before they hold a single key. S3 and every other integration work unchanged, because callers still call KMS.

STEP 04 / 05

KEYS THAT NEVER ENTER AN AWS BUILDING

  1. S3GetObject, INV-0042inside AWS
  2. KMSadds its own layerinside AWS
  3. XKS proxyyou run itoutside AWS, run by you
  4. your key manageroutside AWSoutside AWS, run by you
S3 asks KMS to decrypt a data key
KMS layer ·external layer ·
Fig 6.4Follow the request out of AWS to your proxy and back. The accent hop is the one only you can switch off.

An , launched in December 2022, keeps key material in a key manager you run outside AWS. KMS sends every encrypt and decrypt request for those keys to an XKS proxy, a service you also run, which passes it to your key manager. Data is encrypted twice: once with KMS’s own key material, once with your external key.

That puts a switch in your hands. Turn off the proxy and every ciphertext under those keys becomes unreadable, to AWS as much as to anyone, and every S3 GetObject for such an object fails. The same switch makes availability and latency your problem: each KMS call now waits on a network path and a service that AWS doesn’t operate.

STEP 05 / 05

CLOUDHSM WITH NO KMS IN FRONT

  • requirementkey material must stay outside AWSanswerexternal key store (XKS)
  • requirementbring your own material, delete it on demandanswerimported material (EXTERNAL)
  • requirementsingle-tenant HSMs behind the KMS APIanswerCloudHSM key store
  • requirementPKCS#11, Oracle TDE, TLS offloadanswerstandalone CloudHSM
  • requirementFIPS 140-3 Level 3 validated HSMsanswerany of them, AWS_KMS included
Fig 6.5Match each requirement to its answer. The accent card is the cue that used to mean CloudHSM and now fits every option.

CloudHSM also works without KMS. Applications talk to the cluster directly through PKCS#11, JCE or OpenSSL, so no IAM policy decides who uses a key, no KMS CloudTrail event records the use, and no S3 or EBS integration exists. That fits TLS offload for web servers, Oracle Transparent Data Encryption, and signing with algorithms KMS doesn’t offer.

Older exam questions used “FIPS 140-2 Level 3” as the cue for CloudHSM. KMS’s own HSMs have been FIPS 140-3 Level 3 validated since November 2024 (NIST certificate #4884), so that cue now fits every option. The cues that still point to CloudHSM are single tenancy, exclusive control of the HSMs, and standard crypto APIs.

WHAT YOU NOW KNOW

  • Origin is fixed at creation: AWS_KMS, EXTERNAL, AWS_CLOUDHSM or EXTERNAL_KEY_STORE.
  • Imported material deletes instantly but can’t be regenerated; a CloudHSM key store starts at about $2,336 a month.
  • An external key store keeps key material out of AWS, at the price of owning its availability. 15 calls so far.
deep dive: CloudHSM clusters, XKS proxies and availability

A CloudHSM cluster takes automatic backups, encrypted under a key only the cluster can use, and a backup can be copied to another Region. To back a custom key store, the cluster needs a crypto user named kmsuser that KMS logs in as, and you must not change that user’s password outside KMS. One account can hold up to 10 custom key stores per Region, CloudHSM and external stores counted together. The older hsm1.medium type is FIPS 140-2 validated; hsm2m.medium is FIPS 140-3 Level 3.

An XKS proxy can be reached over the public internet or through a VPC endpoint service in your account. KMS waits a fixed time for each proxy response before it fails the request, so a slow proxy turns into failed S3 reads and failed EBS attaches. Run at least two proxy instances and keep the key manager highly available, because KMS has no copy to fall back on.

ON THE EXAM

  • “keys must be generated and held in HSMs you control exclusively” → CloudHSM, as a custom key store if AWS services must use the key
  • “key material must remain outside AWS” → an external key store (XKS)
  • “must be able to delete key material immediately” → imported key material
  • “use KMS with the least operational overhead” → origin AWS_KMS

Deleting one key to destroy a year of invoices

On 3 January 2034 at 09:00, the retention period for every 2026 invoice has been over for three days. All of them are encrypted under one key, invoices-2026, so one deletion covers the whole year, including the copies in another Region. Two scheduled deletions, one refused read and two deletions run by KMS take Σ from 15 to 20 KMS calls.

STEP 01 / 04

ONE KEY PER YEAR OF INVOICES

  • ledger-invoices · us-east-1the current object● under invoices-2026unreadable after DeleteKey
  • ledger-invoices · us-east-1noncurrent versions, if any● under invoices-2026unreadable after DeleteKey
  • ledger-invoices-dr · us-west-2the replica● under the us-west-2 replica keyunreadable after DeleteKey
  • auditor’s laptop · 444455556666the 2027 download○ plaintext, no KMS keynot reached by any deletion
Fig 7.1Every card with an accent ● line is covered by one deletion. The outlined card, the auditor’s download, is not.

On 2034-01-03, INV-0042 exists in more places than anyone listed: the object in ledger-invoices, any noncurrent versions S3 versioning kept, and the replica in ledger-invoices-dr in us-west-2. Every one is ciphertext under invoices-2026 or its replica. Deleting the key makes all of them unreadable at once, which is called .

The method reaches only ciphertext under that key. The auditor downloaded INV-0042 in 2027, and their copy is plaintext on their own machine, so no KMS deletion touches it. That is why the company keeps one key per year of invoices: when a retention period ends, deleting one key removes a whole year without listing a single object.

STEP 02 / 04

DISABLE IS A SWITCH, DELETE IS A COUNTDOWN

#16 #17
#19
#20
2034-01-012034-02-12
replica · us-west-2 · call #16
PendingDeletion· 7 days, 01-03 → 01-10
primary · us-east-1 · call #17
PendingReplicaDeletion· until the replica is gonePendingDeletion· 30 days, 01-10 → 02-09
Fig 7.2Read the two bars: the primary’s 30 days, in accent, start only where the replica’s 7 days end.

DisableKey takes effect at once and is reversible: the key refuses every request until EnableKey, and it still costs $1 a month. ScheduleKeyDeletion starts a waiting period of 7 to 30 days, 30 by default. During it the key is in , refuses every request and costs nothing. CancelKeyDeletion returns it, disabled.

A multi-Region primary can’t be deleted while a replica exists. At 09:00 key-admin schedules the us-west-2 replica with a 7-day wait (call #16), then the primary with 30 days (call #17). The primary goes to PendingReplicaDeletion, and its 30 days start only when the replica is deleted, on 2034-01-10.

CALLS #16 AND #17: THE REPLICA FIRST, THEN THE PRIMARY
aws kms schedule-key-deletion \
  --region us-west-2 \
  --key-id mrk-1234abcd12ab34cd56ef1234567890ab \
  --pending-window-in-days 7           # call #16, the replica

aws kms schedule-key-deletion \
  --region us-east-1 \
  --key-id mrk-1234abcd12ab34cd56ef1234567890ab \
  --pending-window-in-days 30          # call #17, the primary
#   "KeyState": "PendingReplicaDeletion"
# The primary's 30 days start when its last replica is deleted.
STEP 03 / 04

THE ALARM BUILT FOR THIS WEEK

#18 Decrypt · tax-export via S3errorCode KMSInvalidStateException“… is pending deletion.”
metric filter$.errorMessage = “* is pending deletion.”
alarmKmsPendingDeletionUse ≥ 1 · SNS security-alerts
CancelKeyDeletion?no: the job is retired, the countdown runs
Fig 7.3Follow the refused call from its CloudTrail record to the alarm. The accent text is the error message the filter matches.

At 02:00 on 2034-01-09 a forgotten tax-export job asks S3 for INV-0042. S3 calls Decrypt as the job’s role (call #18), and KMS answers KMSInvalidStateException, with an error message saying the key is pending deletion. Like every refused call, it is written to CloudTrail.

AWS documents a CloudWatch metric filter for exactly this: match KMS events whose error message ends in “is pending deletion.” and alarm on the first one. The alarm reaches the security team the same morning. They confirm the job is retired and let the countdown run. Had the job still mattered, CancelKeyDeletion would have kept every 2026 invoice readable.

THE METRIC FILTER AND ALARM THAT CATCH CALL #18
aws logs put-metric-filter \
  --log-group-name cloudtrail-ledger-prod \
  --filter-name kms-pending-deletion-use \
  --filter-pattern '{ $.eventSource = kms* && $.errorMessage = "* is pending deletion." }' \
  --metric-transformations \
    metricName=KmsPendingDeletionUse,metricNamespace=Security,metricValue=1

aws cloudwatch put-metric-alarm \
  --alarm-name kms-key-pending-deletion-used \
  --namespace Security --metric-name KmsPendingDeletionUse \
  --statistic Sum --period 300 --evaluation-periods 1 \
  --threshold 1 --comparison-operator GreaterThanOrEqualToThreshold \
  --alarm-actions arn:aws:sns:us-east-1:111122223333:security-alerts
STEP 04 / 04

THE LAST TWO EVENTS HAVE NO CALLER

2026/11/INV-0042.pdf
x-amz-server-side-encryptionaws:kms
key…:key/mrk-1234…key deleted 2034-02-09
encrypted data keykey ref + 32 encrypted bytes
ciphertext · 188,416 bytes · still stored and billed
  1. #16ScheduleKeyDeletion (7 days)key-adminok2034-01-03 09:00:00 · us-west-2
  2. #17ScheduleKeyDeletion (30 days)key-adminok2034-01-03 09:01:30 · us-east-1
  3. #18Decrypttax-export via S3KMSInvalidStateException2034-01-09 02:00:00 · us-east-1
  4. #19DeleteKeyKMSok2034-01-10 · us-west-2
  5. #20DeleteKeyKMSok2034-02-09 · us-east-1
Σ KMS calls so far20
Fig 7.4The envelope’s key field is struck through. Rows #19 and #20, in accent, were made by KMS, with no caller.

On 2034-01-10 KMS deletes the replica (call #19), and on 2034-02-09 it deletes the primary (call #20), with every version of its key material. Both are CloudTrail events with eventType AwsServiceEvent and no principal behind them. Nothing can restore the key afterwards, AWS Support included.

INV-0042 is still in ledger-invoices: 188,416 bytes of ciphertext and the encrypted data key beside them, billed as storage until a lifecycle rule removes them. Every request for the object now fails, because the key its envelope names no longer exists. Σ ends at 20 KMS calls.

WHAT YOU NOW KNOW

  • Deleting one key makes every copy encrypted under it unreadable; plaintext copies elsewhere are out of reach.
  • Disabling is instant and reversible; deletion waits 7–30 days, and a multi-Region primary waits for its replicas.
  • The waiting period exists to catch calls like #18; after DeleteKey the ciphertext stays and can’t be read. 20 calls.
deep dive: Object Lock, lifecycle rules, and what shredding cannot reach

S3 Object Lock in compliance mode stops anyone deleting an object before its retention date, the root user included. Deleting the key still makes a locked object unreadable, so a team that relies on Object Lock for retention has to align key deletion with the lock dates. After the key is gone, a lifecycle rule removes the ciphertext and stops the storage charge.

EBS and RDS snapshots encrypted under a deleted key can never be restored. A running RDS instance whose key is disabled or deleted moves to the inaccessible-encryption-credentials state. An AWS Config rule can flag keys scheduled for deletion across accounts. For keys with imported material, DeleteImportedKeyMaterial is an instant alternative that can be undone by importing the same material again. The actual waiting period can run up to 24 hours longer than the one you scheduled.

ON THE EXAM

  • “a key scheduled for deletion may still be in use” → alarm on CloudTrail events for the key, then CancelKeyDeletion
  • “stop all use of a key immediately and reversibly” → DisableKey
  • “destroy the data in every backup and replica” → delete the key (crypto-shredding)
  • “shortest wait to delete a key KMS generated” → 7 days

Twenty KMS calls, and the trail each one left

Seven years and three months of one invoice, counted once more. The board sorts the 20 calls by who made them, what they asked KMS to do, how KMS answered, and which CloudTrail log holds the record. The last block is the one to keep for the exam: a refused cross-account call is in the caller’s log, not the key owner’s.

INV-0042.pdf · 2026-11-05 → 2034-02-09
20272028202920302031203220332034
one tick per call; the 3 refused calls in purple
BY CALLER
S3, acting for a person or role9 calls
people managing the key8 calls
KMS itself, on a schedule3 calls
BY OPERATION
Decrypt7 calls
GenerateDataKey1 call
Encrypt (replication)1 call
key management (create, alias, policy, rotation, replicate, describe, schedule deletion)8 calls
RotateKey, DeleteKey (no caller)3 calls
BY OUTCOME
succeeded17 calls
refused by the key policy2 calls
key pending deletion1 call
WHERE EACH CALL IS LOGGED
ledger-prod, us-east-116 calls
ledger-prod, us-west-23 calls
auditor’s account (1 of them nowhere else)2 calls
Σ KMS calls made for one invoice20
Read next. AWS VPC: how one request crosses a private network for the network side of the same bucket: gateway endpoints, endpoint policies, and a bucket policy that accepts only one endpoint with aws:SourceVpce.
eventName DeleteKey · eventType AwsServiceEvent