Right, so you’ve got your KMS key all set up. Its policy is a beautiful, meticulously crafted document of who-can-do-what. It’s perfect. And then your boss walks in and says, “Hey, we need to let this other AWS account over here use this key, but only for a specific thing, and only for the next 24 hours. And please don’t touch the key policy, Brenda in security will have a fit.”

You don’t panic. You reach for the duct tape and baling wire of KMS permissions: grants.

Think of a grant as a scoped, temporary, and revocable “hall pass” for a specific principal (like an IAM user/role or an AWS service) to use a KMS key. It’s a separate permission slip you write that bypasses the need to even mention this principal in the key policy itself. It’s delegation without desecration.

The magic, and the complexity, lies in that scope. A grant answers four questions:

  1. Who gets the permission? (the GranteePrincipal)
  2. Which key can they use? (the KeyId)
  3. What can they do with it? (the Operations, like Encrypt, Decrypt, GenerateDataKey)
  4. Under what conditions is this allowed? (the Constraints)

The Anatomy of a Grant

Let’s make one. The most common use case is letting another AWS account use your key. Here, I’m the account owner (account 111122223333) and I’m granting a role in another account (444455556666) permission to decrypt with my key.

import boto3
import json

# Client in the key's OWNER account (111122223333)
kms_client = boto3.client('kms', region_name='us-east-1')

key_id = 'arn:aws:kms:us-east-1:111122223333:key/1234abcd-12ab-34cd-56ef-1234567890ab'
grantee_principal = 'arn:aws:iam::444455556666:role/MyCrossAccountRole'
operations = ['Decrypt']  # They ONLY get to decrypt. Not encrypt, not anything else.

response = kms_client.create_grant(
    KeyId=key_id,
    GranteePrincipal=grantee_principal,
    Operations=operations
)

print(f"Grant created with ID: {response['GrantId']}")
# Hold onto this GrantId! You'll need it to revoke this thing later.

The key here (no pun intended) is that the role in account 444455556666 must also have IAM permissions that allow it to assume the identity of the grant. This usually means an IAM policy that allows kms:Decrypt but with a crucial condition: the encryption context.

The Crucial Role of Encryption Context

This is where everyone faceplants. AWS isn’t just going to take the grant’s word for it. For decryption, the caller must provide the exact same encryption context that was used during the encryption. It acts as a cryptographic checksum and a permission boundary.

Imagine the encryption context as a secret handshake. You encrypt the data with the handshake, you must decrypt with the same handshake. The grant is your ID card that says you’re allowed to use that handshake.

Here’s how the decrypt call must look from the other account:

# This code runs in the GRANTEE account (444455556666), assuming the MyCrossAccountRole
kms_client_grantee = boto3.client('kms', region_name='us-east-1')

ciphertext_blob = b'...' # your encrypted data blob

# This encryption context MUST match the one used during encryption exactly.
encryption_context = {
    "Project": "Phoenix",
    "Environment": "Production"
}

plaintext = kms_client_grantee.decrypt(
    CiphertextBlob=ciphertext_blob,
    EncryptionContext=encryption_context
)['Plaintext']

If you mess up the encryption context—even by a single character—you get an AccessDeniedException. Not a InvalidCiphertextException, mind you, but an access denied. This is KMS’s way of saying, “Your hall pass is for the chemistry lab, but you’re trying to get into the gym. Permission denied.” It’s infuriatingly opaque until you know the trick.

The Retiree Principal: For When AWS Services Need a Hall Pass

Sometimes the “who” isn’t another account, but an AWS service itself. Many services, like AWS Lambda or Secrets Manager, need to use your KMS key on your behalf. They can’t be listed in your key policy directly because… well, they’re a service, not a fixed principal.

For this, you use a special principal called the retiring principal. It’s usually the service’s own service-linked role or service principal.

# Granting AWS Lambda permission to use your key for a specific function
response = kms_client.create_grant(
    KeyId=key_id,
    GranteePrincipal='arn:aws:iam::111122223333:role/my-lambda-execution-role',
    RetiringPrincipal='arn:aws:iam::111122223333:role/aws-service-role/autoscaling.amazonaws.com/AWSServiceRoleForAutoScaling', # Example, check the service's docs!
    Operations=['GenerateDataKey', 'Decrypt']
)

The RetiringPrincipal is the service that will eventually “retire” or revoke the grant when it’s no longer needed. It’s a weird bit of foresight in the API design.

The Dark Side: Pitfalls and Revocation

Grants are brilliant, but they have a dark side: they are a shadow permission system. They exist outside the key policy, making them easy to create and horrifyingly easy to lose track of.

  • They are not listed in the console. You cannot easily see all active grants on a key through the AWS Management Console. You must use the CLI or API (list-grants).
  • They are silent. If a grant is missing or its conditions aren’t met, you get AccessDenied, which is a nightmare to debug.
  • They can create spaghetti permissions. A key can have many grants. If you’re not meticulous, you’ll end up with a web of temporary permissions that are, in fact, permanent because you forgot about them.

Which brings us to the most important part: cleanup.

# Use the GrantId you (hopefully) stored when you created the grant
aws kms revoke-grant \
    --key-id arn:aws:kms:us-east-1:111122223333:key/1234abcd... \
    --grant-id abcdef1234567890abcdef1234567890abcdef12

If you lose the GrantId, you’re in for a world of hurt. You have to list all grants and try to identify the one you need to revoke based on its properties. My advice? Always tag your grants with a name and expiration date in the constraints, and always store the GrantId somewhere you can find it later. Your future self, debugging at 2 AM, will thank you.

Grants are the ultimate power tool for nuanced access in KMS. They’re what make the system flexible enough for real-world use. But like all power tools, respect them, because they will absolutely bite you if you get careless.