skip to content

What is the encryption context in an AWS KMS Encrypt or GenerateDataKey request, and what does supplying one give you that the key policy alone does not?

level: seniorimportance: nice to knowfreq 30%

answer

  1. extra key-value pairs on the request
  2. must match exactly on decrypt
  3. shows up in the CloudTrail event
  4. condition keys and grant constraints
  5. never put a secret in it

basics

~20 s

An encryption context is a set of non-secret key-value pairs bound to the ciphertext. Decrypt must supply exactly the same pairs or it fails, the pairs appear in CloudTrail, and IAM condition keys can scope access by them.

solid answer

~50 s

It is a map of non-secret string pairs — something like `{"tenant":"acme","purpose":"invoice"}` — that you pass on `Encrypt` or `GenerateDataKey`. KMS binds it to the ciphertext as additional authenticated data, so a later `Decrypt` must supply the identical pairs or the call fails; you cannot take a blob issued for one tenant and unwrap it in another tenant's request. Beyond that integrity binding it gives two things a key policy cannot. First, audit: the context is recorded in the CloudTrail event, so a `Decrypt` log line tells you *which* object was unwrapped, not just that someone used the key. Second, authorization granularity: policies and grants can test it with condition keys such as `kms:EncryptionContext:tenant` and `kms:EncryptionContextKeys`, so one shared key can be scoped per tenant. It is not secret — never put anything sensitive in it.

go deeper

for a junior

Know that KMS accepts optional key-value pairs alongside an encrypt request and that the same pairs must be supplied to decrypt. Remember that they are visible, not secret.

for a middle

Explain that the context is bound to the ciphertext rather than stored in it, so your code must reconstruct it, and that a mismatch causes the decrypt to fail rather than to return wrong data.

for a senior

Demonstrate the two payoffs a key policy cannot give: CloudTrail events that identify which object was unwrapped, and condition keys such as kms:EncryptionContext:<key> that scope one shared key per tenant.

for a principal

Own it as a schema decision. The context is a wire contract whose keys can never change without a re-encryption programme, so define it once, version it, and rule on what may never appear in it because CloudTrail is not a secret store.

## The mechanism Most KMS operations on a symmetric key accept an optional `EncryptionContext`: a map of string keys to string values that you choose. KMS passes it as additional authenticated data to the encryption operation, which means it is cryptographically bound to the ciphertext but not stored inside it — it is not confidential, and it is not recoverable from the blob. The binding produces one hard rule: **a `Decrypt` call must supply exactly the same pairs that `Encrypt` or `GenerateDataKey` was given, or the call fails.** Order does not matter (it is a map, not a list), but keys and values are case-sensitive and every pair must be present. Because the context is not stored in the ciphertext, your application must be able to reconstruct it at decrypt time from something it already knows — the tenant ID on the request, the object key, the row's primary key. That constraint is a feature: it forces the context to be derived from the caller's own view of what it is reading. Encryption context applies to symmetric operations. Asymmetric encryption with a KMS key does not support it. ## Three things it buys **Integrity binding.** The context ties a wrapped data key to the thing it was meant for. If a service can be tricked into feeding it a blob belonging to another tenant, the decrypt fails rather than succeeding and returning the wrong tenant's key. That converts a silent cross-tenant read into a loud error. **Audit resolution.** KMS records the encryption context in the CloudTrail event for the operation. Without it, a `Decrypt` event tells you a principal used a key at a point in time — true, and nearly useless during an investigation, because a shared key is used constantly. With it, the same event tells you which tenant, which document, which purpose. This is often the single strongest argument for adopting it. **Authorization granularity.** The pairs are exposed as condition keys, so policy can reason about them: ```json { "Effect": "Allow", "Principal": { "AWS": "arn:aws:iam::111122223333:role/tenant-worker" }, "Action": "kms:Decrypt", "Resource": "*", "Condition": { "StringEquals": { "kms:EncryptionContext:tenant": "acme" } } } ``` `kms:EncryptionContextKeys` tests which keys are present rather than their values, which is how you enforce "every encrypt must declare a tenant" without enumerating tenants. Grants take the same idea further with the constraints `EncryptionContextEquals` and `EncryptionContextSubset`, so a grant issued to a worker can be scoped to exactly one tenant's context for its lifetime. Together these let a single KMS key serve many tenants with per-tenant authorization, which matters enormously for cost and quota when the alternative is one key per tenant. ## What it is not It is **not secret**. It travels in the request, it is logged in CloudTrail, and anyone with read access to those logs sees it. Putting a customer name might be acceptable; putting an email address, an account number, or anything else you would not want in a log aggregator is a data-leak defect. It is **not a substitute for authorization**. Supplying a context does not grant anything; it only supplies a value that policy may test. A caller with blanket `kms:Decrypt` and no condition on the key can pass any context they like — including the correct one, if they can guess it. It is **not free of operational risk**. Because decrypt must reproduce the context byte-for-byte, an encoding change, a case change, a trimmed whitespace, or a refactor that renames a context key makes existing ciphertext undecryptable. Treat the context format as a wire contract: version it, keep it derived from stable identifiers, and never include anything mutable such as a display name or a timestamp. ## Where you will meet it without asking Several AWS services set an encryption context for you when they call KMS on your behalf, typically naming the resource ARN involved. That is why CloudTrail events for service-driven KMS use often already identify the resource, and it is why you can write key policies that constrain a service to a specific resource — the context values are the hook the condition attaches to. When you write your own envelope encryption, you are expected to supply the equivalent yourself.

  • Where is the encryption context stored, and what does that imply for your application?
    It is not stored in the ciphertext blob — it is bound to it, not embedded. Your application has to reconstruct the identical map at decrypt time from information it already holds, such as the tenant ID or object key on the incoming request. That is why the context must be derived from stable identifiers rather than from mutable metadata.
  • Can an encryption context make a shared KMS key safe for multiple tenants?
    It can, if the authorization is written to depend on it: each tenant's worker gets `kms:Decrypt` conditioned on `kms:EncryptionContext:tenant` matching its own tenant, or holds a grant with an `EncryptionContextEquals` constraint. Without such a condition the context is only an audit and integrity aid — a caller with unconditional Decrypt can pass any context.
  • What breaks if a team renames a context key from tenantId to tenant?
    Every object encrypted under the old name becomes undecryptable, because Decrypt requires an exact match on keys and values. There is no server-side migration for this — you must decrypt with the old context and re-encrypt with the new one while both code paths exist, which is why the context format should be treated as a versioned wire contract.

saying these in an interview costs you the question

  • Believes the encryption context is confidential
  • Puts personal data or secrets in the context
  • Thinks supplying a context by itself grants access
  • Assumes the context is stored inside the ciphertext blob
  • Expects decrypt to succeed with a partial or renamed context

context