KMS & crypto - keys and envelopes
Cloud encryption starts at the KMS: whoever has access to the key decides what is 'encrypted'. Without governing keys, 'encrypted at rest' is just a checkbox in the console.
What it is
KMS () is the component that creates, stores, uses and revokes cryptographic keys without exposing the secret to your code. The application asks the KMS to 'encrypt this' or 'decrypt this', and the key never leaves the key service. The rest of the system uses the ciphertext.
This detail matters because most encryption failures happen on the wrong plane: keys in environment variables, keys committed to Git, keys in config files. Taking the key out of the application's path is what the KMS does.
Why 'encrypted at rest' is only the beginning
Storage services already come with encryption at rest enabled by default (S3 SSE, EBS encryption, RDS storage encryption). This protects the disk against physical removal, but not against whoever has API access: someone with s3:GetObject reads the encrypted object and asks the KMS to decrypt it.
If the key policy allows overly broad use, any identity that already has read permission gets effective access to the plaintext. The right question is not 'is it encrypted?', but 'who can ask the KMS to decrypt this?'.
Envelope encryption: key protects key
Sending an entire large file to the KMS on every encryption is expensive and slow. The standard solution is : the KMS generates a symmetric data key, the payload is encrypted locally with AES, and the data key is encrypted with the KMS master key. The result travels with the data.
In the flow below, the client receives the plaintext and ciphertext of the data key, encrypts the document with the local key, and discards the plaintext. Only the data key's ciphertext stays persisted next to the file.
import boto3
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
kms = boto3.client("kms")
KEY_ID = "arn:aws:kms:us-east-1:111122223333:key/abcd1234-..."
def envelope_encrypt(plaintext: bytes, kms_key_id: str = KEY_ID):
# 1. KMS returns a plaintext data key + the same key encrypted
resp = kms.generate_data_key(KeyId=kms_key_id, KeySpec="AES_256")
plaintext_key = resp["Plaintext"]
encrypted_key = resp["CiphertextBlob"]
# 2. encrypts the payload locally, never sending it to the KMS
nonce = b"\x00" * 12
ciphertext_body = AESGCM(plaintext_key).encrypt(nonce, plaintext, None)
# 3. discards the data key plaintext, only ciphertext travels with the file
del plaintext_key
return {"encrypted_key": encrypted_key, "ciphertext_body": ciphertext_body, "nonce": nonce}Key policy: who uses the key
The key policy is the document that governs a KMS key. It decides who can use the key, for which operations and under which conditions. The default for AWS-generated keys is liberal: the root account can do everything, but if nobody restricts it, any principal with kms:* across the whole environment gets through.
A good key policy separates administrative use (create, rotate, delete) from cryptographic use (encrypt, decrypt). The application team only gets the second, via grants limited to a bucket or table ARN pattern.
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AdminsManageKey",
"Effect": "Allow",
"Principal": { "AWS": "arn:aws:iam::111122223333:role/SecTeam" },
"Action": [
"kms:Create*", "kms:Describe*", "kms:Enable*", "kms:Disable*",
"kms:ScheduleKeyDeletion", "kms:RotateKeyOnDemand"
],
"Resource": "*"
},
{
"Sid": "AppEncryptsOnly",
"Effect": "Allow",
"Principal": { "AWS": "arn:aws:iam::111122223333:role/app-frontend" },
"Action": [
"kms:Encrypt", "kms:Decrypt", "kms:GenerateDataKey", "kms:DescribeKey"
],
"Resource": "*",
"Condition": {
"StringEquals": {
"kms:ViaService": "s3.us-east-1.amazonaws.com"
}
}
}
]
}Rotation and revocation
Automatic annual rotation is enabled by default on KMS keys and costs almost nothing to use: the old key remains valid for decrypting old data, and the new one encrypts the next. When you need to, you force manual rotation after suspected misuse.
Revocation is different: you either disable the key (blocks decryption) or schedule its deletion. Blocking the key breaks legitimate applications that try to read old data. Hence the practical rule: monitor usage, separate keys by sensitivity, and never mix a production key with a test key on the same resource.
Where it fits in day-to-day work
- standard storage: S3, EBS, RDS, DynamoDB and similar already encrypt with KMS, but the key policy must say who can call decryption
- envelope encryption: sensitive payloads in a queue or blob go through the local key + encrypted data key pattern
- BYOK/HYOK: regulated companies import their own key (Bring Your Own Key) or keep the key in an external HSM (Hold Your Own Key)
- auditing: every GenerateDataKey, Encrypt or Decrypt call becomes a CloudTrail event with principal ARN and
Strengths
- the key never touches the application: even an attacker with RCE on the server cannot extract the key from the KMS
- centralized rotation: rotating in one place migrates the whole tree of data keys in use
- native : every use is signed and logged with identity, resource and timestamp
Limitations
- per-call cost: KMS charges per operation, and a badly designed envelope turns into a heavy bill per request
- latency per hop: each Encrypt/Decrypt adds a trip to the KMS, and latency-sensitive paths need a locally cached key
- BYOK/HYOK adds operational complexity: HSMs in two regions, manual rotation, and separate disaster recovery procedures
connections on the map
- IAM - identity and accessauthorizes key accessKMS & crypto - keys and envelopes
- KMS & crypto - keys and envelopesencrypts the secretsSecrets - vaults and rotation
updated on 14/10/2026 · sources to go deeper · report an error
in other domains
- A04 Cryptographic Failuresowaspmanaged keys and encryption, with no homegrown code
- Vector DBai-agentsencrypt the index with a managed key
Study before
Unlocks
test what you learned
Loading questions…