Encryption at rest and secret handling are baseline requirements for any production or regulated workload.
AWS KMS manages encryption keys inside hardware security modules; key material never leaves KMS unencrypted. Key types: AWS owned (invisible, used by some services), AWS managed (aws/s3, aws/ebs: free, automatic, but you can't edit their policies or share them across accounts) and customer managed keys (about $1 per month, full control over key policy, rotation and grants).
Encrypt handles at most 4 KB, so real data uses envelope encryption: GenerateDataKey returns a data key twice, as plaintext and encrypted under the KMS key. You encrypt locally with the plaintext key, throw it away, and store the encrypted key next to the data. To read, call Decrypt on the small encrypted key. S3, EBS, RDS and DynamoDB do exactly this when you choose SSE-KMS, which is why one checkbox gives you encryption with auditable key use in CloudTrail.
Access to a key needs the key policy to allow it (directly or by delegating to IAM). Enable automatic rotation on customer managed keys; old material is retained so existing ciphertext still decrypts.
Secrets Manager stores secrets such as database passwords and API tokens, encrypted with KMS, versioned with staging labels (AWSCURRENT, AWSPENDING), with built-in rotation for RDS and Aurora, and optional cross-Region replication. Applications fetch secrets at runtime with their role and cache them. SSM Parameter Store SecureString is a cheaper option for configuration values that don't need rotation.
Never put secrets in environment variables of templates, AMIs, container images or Git.
Example
import json, boto3
sm = boto3.client("secretsmanager", region_name="eu-west-1")
sm.create_secret(Name="prod/orders/db",
SecretString=json.dumps({"user": "app", "password": "s3cr3t"}))
db = json.loads(sm.get_secret_value(SecretId="prod/orders/db")["SecretString"])
print(db["user"]) # app
kms = boto3.client("kms", region_name="eu-west-1")
key_id = kms.create_key(Description="orders data")["KeyMetadata"]["KeyId"]
# Envelope encryption: use the plaintext key locally, store only the blob
dk = kms.generate_data_key(KeyId=key_id, KeySpec="AES_256")
print(len(dk["Plaintext"])) # 32 bytes, never persist this
blob = dk["CiphertextBlob"] # store next to the encrypted data
plain = kms.decrypt(CiphertextBlob=blob)["Plaintext"]
print(plain == dk["Plaintext"]) # TrueRemember
- KMS keys never leave KMS; data is encrypted with data keys (envelope encryption)
- Customer managed keys give you key policy, rotation and cross-account control
- Secrets Manager stores, versions and rotates secrets; fetch and cache at runtime
Watch out for
- Using an AWS managed key (aws/ebs) for snapshots you later need to share with another account.