KMS (Key Management Service)
AWS KMS (Key Management Service) is a managed service that creates and controls the encryption keys used to protect your data. Instead of inventing your own way to store secret keys (which almost always ends badly), you let KMS hold the master key in tamper-resistant hardware, and you ask KMS to encrypt or decrypt on your behalf. Nearly every AWS service — S3, EBS, RDS, DynamoDB, Secrets Manager, and more — can be told “encrypt my data with this KMS key,” which is why KMS is the foundation of encryption across AWS.
What a KMS key actually is
A KMS key (formerly called a “Customer Master Key” or CMK) is a logical key that lives inside KMS and never leaves it in plaintext. You can send up to 4 KB of data to KMS and get back ciphertext, or send ciphertext back and get plaintext — but the key material itself stays locked inside KMS. This is the whole point: even your own administrators cannot copy the raw key out.
Keys come in two cryptographic flavours:
| Key type | What it does | When to use |
|---|---|---|
| Symmetric | One key both encrypts and decrypts. | The default. Use for encrypting data at rest in S3, EBS, RDS, etc. Fastest and cheapest. |
| Asymmetric | A public/private key pair (RSA or ECC). | Use when an outside party needs the public key to encrypt or verify signatures, but you keep the private key. Less common. |
Pick symmetric unless you have a specific need to share a public key or do digital signing. Most AWS service integrations only work with symmetric keys.
AWS-managed vs customer-managed keys
There are two ways a key can come into existence, and the difference matters for control and cost.
| AWS-managed key | Customer-managed key (CMK) | |
|---|---|---|
| Created by | AWS, automatically, the first time you enable encryption in a service | You, explicitly |
| Key policy control | None — you cannot edit it | Full — you write the policy |
| Key rotation | Automatic, yearly | Optional, you turn it on |
| Monthly cost | Free | ~$1 per key per month |
| Audit / cross-account sharing | Limited | Full |
When to use AWS-managed keys: quick start, you just want encryption on and don’t need to control policy. When to use customer-managed keys: you need to define exactly who can use the key, share it across accounts, audit every use in CloudTrail, or disable/schedule deletion. Most production setups use customer-managed keys.
Envelope encryption (how big data gets encrypted fast)
KMS will only directly encrypt up to 4 KB. So how does it encrypt a 5 GB S3 object? Through envelope encryption: KMS generates a fresh data key (a normal AES-256 key), gives you two copies — one plaintext and one encrypted under your KMS key — and the service encrypts the large object locally with the plaintext data key, then throws the plaintext copy away and stores the encrypted data key next to the object. To read the object later, the service sends the encrypted data key back to KMS to be decrypted. This means the slow KMS call only happens once per object, not per byte.
aws kms generate-data-key \
--key-id alias/app-data-key \
--key-spec AES_256
Output:
{
"CiphertextBlob": "AQIDAHj...base64...==",
"Plaintext": "tXX9...base64 AES key...=",
"KeyId": "arn:aws:kms:us-east-1:111122223333:key/1234abcd-12ab-34cd-56ef-1234567890ab"
}
Creating and using a customer-managed key
Console
- Open the KMS console and choose Customer managed keys > Create key.
- Choose Symmetric and Encrypt and decrypt, then Next.
- Give it an alias (e.g.
app-data-key) and a description. - On Define key administrative permissions, pick the IAM roles/users who can manage the key.
- On Define key usage permissions, pick who can encrypt/decrypt with it.
- Review the generated key policy and choose Finish.
CLI
# Create the key and capture its ID
aws kms create-key \
--description "App data encryption key" \
--key-usage ENCRYPT_DECRYPT \
--key-spec SYMMETRIC_DEFAULT
# Give it a friendly alias
aws kms create-alias \
--alias-name alias/app-data-key \
--target-key-id 1234abcd-12ab-34cd-56ef-1234567890ab
Output:
{
"KeyMetadata": {
"KeyId": "1234abcd-12ab-34cd-56ef-1234567890ab",
"Arn": "arn:aws:kms:us-east-1:111122223333:key/1234abcd-12ab-34cd-56ef-1234567890ab",
"KeyState": "Enabled",
"KeySpec": "SYMMETRIC_DEFAULT"
}
}
Key policies and grants
This is the part that trips everyone up. A key policy is a resource policy attached directly to the KMS key, and it is the primary authority over the key — independent of IAM. For a request to succeed, the key policy must allow it (either directly or by delegating to IAM with a kms:* statement that has the account root as Principal).
Critical gotcha: A user with full IAM admin rights can still be completely denied access to a key if the key policy does not allow them. If you create a key and forget to add yourself (or the account root) to the key policy, you can lock everyone out of the key — and therefore out of every piece of data it encrypted. AWS cannot recover this for you. Always keep a statement that allows
kms:*to the account root so IAM can grant access.
Grants are a more temporary, fine-grained way to give another principal permission to use a key for a specific operation, often used by AWS services (like Auto Scaling using an EBS key) without editing the policy. Use grants for short-lived, programmatic delegation; use the key policy for stable, broad permissions.
resource "aws_kms_key" "app" {
description = "App data encryption key"
enable_key_rotation = true
deletion_window_in_days = 30
}
resource "aws_kms_alias" "app" {
name = "alias/app-data-key"
target_key_id = aws_kms_key.app.key_id
}
Cost and the high-throughput trap
KMS pricing has two parts: ~$1 per customer-managed key per month, plus a charge per API call (about $0.03 per 10,000 requests). A single key is cheap, but a busy app that decrypts millions of small S3 objects can rack up huge KMS request bills, because every object read is a KMS Decrypt call.
The fixes:
- S3 Bucket Keys — enable this on an S3 bucket so S3 generates one data key per bucket and reuses it, cutting KMS calls (and cost) by up to 99%.
- Caching — for application-side encryption, use the AWS Encryption SDK with a data key cache so one data key serves many records.
aws s3api put-bucket-encryption \
--bucket my-app-bucket \
--server-side-encryption-configuration '{
"Rules": [{
"ApplyServerSideEncryptionByDefault": {
"SSEAlgorithm": "aws:kms",
"KMSMasterKeyID": "alias/app-data-key"
},
"BucketKeyEnabled": true
}]
}'
Best Practices
- Always keep a key policy statement allowing
kms:*for the account root, so you can never lock yourself out. - Use customer-managed keys (not AWS-managed) when you need policy control, auditing, or cross-account sharing.
- Turn on automatic yearly key rotation — old data stays readable and you get fresh key material for free.
- Enable S3 Bucket Keys on high-traffic buckets to slash per-request KMS costs.
- Use aliases (e.g.
alias/app-data-key) in code instead of raw key IDs so you can rotate the underlying key without code changes. - Set a deletion window (7-30 days) so scheduled key deletions can be cancelled before data becomes permanently unrecoverable.
- Audit
kms:Decryptandkms:Encryptcalls in CloudTrail to spot unexpected access.