32.5 AWS Managed Keys vs Customer Managed Keys vs Customer Provided Keys
Right, let’s talk about the three flavors of keys in KMS. This isn’t just a menu of options; it’s a fundamental choice about who holds the keys to your kingdom—you, AWS, or a weird shared custody arrangement. Getting this wrong is a fantastic way to either create a management nightmare or accidentally lock yourself out of your own data. So pay attention.
The Quick ‘What Are They?’ Breakdown
- AWS Managed Keys (SSE-KMS): The key AWS creates and manages for you automatically when you select the “aws/kms” option in a service like S3 or EBS. You never see the key material, and its policy is entirely controlled by AWS. It’s the “just make it work” option.
- Customer Managed Keys (CMKs): These are the keys you create in your own account. You control their key policy, define who can use them, enable/disable them, and rotate them. This is where you go for any serious, application-level encryption. This is our main character.
- Customer Provided Keys (Import Your Own Key): This is the “hold my beer” option. You generate your own encryption key material externally and import it into KMS. KMS will then use your key material to perform its cryptographic operations. It’s for the ultra-paranoid (or those with specific compliance needs) who don’t trust AWS to even generate the key.
Why You Should Almost Always Use Customer Managed Keys
AWS Managed Keys are seductively easy. Click a dropdown, and boom, encryption. But they come with a massive, hilarious caveat: their permissions are often wildly over-permissive. The default key policy for an AWS-managed key often grants encryption/decryption permissions to the service itself across your entire account. If an IAM user in your account can access the S3 bucket, they can probably decrypt its contents, because the S3 service is allowed to use the key on their behalf. You’ve encrypted the data, but you haven’t really controlled access to the key.
A Customer Managed Key fixes this. You write the key policy. You can be surgical. You can say, “This IAM role can encrypt, but only this other role from this specific VPC can decrypt.” You own the liability and the control. It’s the difference between renting a room in a house where the landlord has a master key (AWS Managed) and owning the house and changing all the locks yourself (CMK).
Here’s how you create one and a policy that’s actually sensible:
# Create the key (note the clever, descriptive name)
aws kms create-key --description "my-app-prod-db-encryption-key"
But the magic is in the policy. You can’t just rely on the default. Attach a policy like this via aws kms put-key-policy to truly lock it down:
{
"Id": "key-consolepolicy-3",
"Version": "2012-10-17",
"Statement": [
{
"Sid": "Enable IAM User Permissions",
"Effect": "Allow",
"Principal": {"AWS": "arn:aws:iam::123456789012:root"},
"Action": "kms:*",
"Resource": "*"
},
{
"Sid": "Allow access for Key Administrators",
"Effect": "Allow",
"Principal": {"AWS": "arn:aws:iam::123456789012:role/SecurityAdmin"},
"Action": [
"kms:Create*",
"kms:Describe*",
"kms:Enable*",
"kms:List*",
"kms:Put*",
"kms:Update*",
"kms:Revoke*",
"kms:Disable*",
"kms:Get*",
"kms:Delete*",
"kms:TagResource",
"kms:UntagResource",
"kms:ScheduleKeyDeletion",
"kms:CancelKeyDeletion"
],
"Resource": "*"
},
{
"Sid": "Allow use of the key for my app",
"Effect": "Allow",
"Principal": {"AWS": "arn:aws:iam::123456789012:role/my-app-prod-role"},
"Action": [
"kms:Encrypt",
"kms:Decrypt",
"kms:ReEncrypt*",
"kms:GenerateDataKey*",
"kms:DescribeKey"
],
"Resource": "*"
}
]
}
This policy gives full admin to your account root (a necessary evil), gives a specific SecurityAdmin role all management powers, and only grants usage permissions to your specific application role. This is how you do least privilege.
The Bizarre World of Importing Your Own Key
Importing your own key (a Customer Provided Key) is a complex ritual involving asymmetric encryption and a lot of steps. You generate a key and its material on your own HSM or secure machine, then you import it into KMS. Why would you do this? Two reasons:
- Compliance: Some regulations literally say you must generate the key yourself.
- Theoretical Extra Paranoia: You can ensure no one, not even AWS, ever had access to the plaintext key material. You can also create a key in KMS that you can delete, making the data permanently unrecoverable, which is a feature for some threat models.
The process is absurdly convoluted. You have to download a public wrapping key from KMS, use openssl to encrypt your key material with it, and then upload the ciphertext. It’s a whole thing.
# This is a simplified glimpse into the madness
aws kms get-parameters-for-import --key-id my-imported-key --wrapping-algorithm RSAES_OAEP_SHA_256 --wrapping-key-spec RSA_2048 > params.json
# Now use openssl to encrypt your key material using the public key from params.json
openssl pkeyutl -encrypt -in PlaintextKeyMaterial.bin -out EncryptedKeyMaterial.bin -pubin -inkey <(jq -r '.PublicKey' < params.json | base64 -D) -keyform DER -pkeyopt rsa_padding_mode:oaep -pkeyopt rsa_oaep_md:sha256
# Finally, import it
aws kms import-key-material --key-id my-imported-key --encrypted-key-material fileb://EncryptedKeyMaterial.bin --import-token fileb:///tmp/ImportToken.bin --expiration-model KEY_MATERIAL_DOES_NOT_EXPIRE
See? I told you. You do this when you have to, not because you want to.
The Critical Gotcha: Asymmetric Costs
Here’s the bit AWS hopes you’ll gloss over: KMS API calls cost money. Encrypt, Decrypt, GenerateDataKey—they all cost $0.03 per 10,000 requests. This seems cheap until you realize a busy application can make billions of requests. Using a CMK to encrypt every object uploaded to S3? That’s an API call. Downloading it? Another. This adds up shockingly fast compared to the free AES256 encryption S3 offers natively (SSE-S3). You’re not just paying for the key storage ($1/month); you’re paying for its usage. Always factor this operational cost into your design. Using KMS is a conscious choice to trade cost for granular control and auditability. Choose wisely.