Encryption & Secrets Management
Keep data safe at rest and keep credentials out of your code with KMS, SSM Parameter Store, and Secrets Manager.
Introduction
Two of the most common cloud security failures are unencrypted data at rest and hardcoded credentials in source code. AWS provides three purpose-built services that solve both problems: KMS manages the encryption keys that protect your data across nearly every AWS service, SSM Parameter Store provides a free hierarchical config store for non-rotating secrets and config values, and Secrets Manager handles full credential lifecycle management including automatic rotation. Understanding when to reach for each one is a core skill for any production AWS deployment.
Quick Navigation
1. AWS KMS
Keys, envelope encryption, SSE-KMS
2. SSM Parameter Store
Hierarchical config and SecureString
3. Secrets Manager
Automatic rotation and credential lifecycle
4. SSM vs Secrets Manager
Cost, rotation, limits, and decision guide
1. AWS KMS - Key Management Service
KMS is the cryptographic backbone of AWS. It creates, stores, and controls access to customer managed keys (CMKs) backed by hardware security modules (HSMs). The key material never leaves the KMS service in plaintext. When you enable encryption on an S3 bucket, an EBS volume, or an RDS database, KMS is doing the key management work underneath every service call.
Purpose
KMS manages the encryption keys that protect data across nearly every AWS service. You define who can use a key via a key policy, a resource-based policy attached directly to the key, and KMS enforces that policy on every Encrypt, Decrypt, and GenerateDataKey API call.
Envelope Encryption
KMS never encrypts large payloads directly. Instead it uses envelope encryption: KMS generates a short-lived data key, your application uses that key to encrypt the actual payload locally (fast, no size limit), and the data key itself is encrypted by the CMK and stored alongside the ciphertext. To decrypt, the process reverses. The CMK never leaves KMS, only the data key travels to your application, briefly.
Envelope Encryption Flow
Encryption: KMS provides the data key. Decryption: KMS unwraps the stored encrypted key. The CMK never leaves KMS.
Key Types
Customer Managed Keys (CMK)
- You create and own the key policy
- Optional automatic annual rotation
- Shareable cross-account and cross-region (multi-region keys)
- $1/month per key + $0.03 per 10k API calls
AWS Managed Keys
- Created automatically when you enable encryption on a service (e.g.,
aws/s3) - Rotate annually (every year) automatically, at no charge
- Cannot be shared cross-account
- Free, but you cannot customize the key policy
KMS Encrypts Data Across AWS Services
Services call KMS to encrypt and decrypt their data keys. A single CMK can protect data across multiple services simultaneously.
When to Use KMS
- Enable SSE-KMS on S3 buckets, EBS volumes, or RDS instances that hold sensitive or regulated data
- Encrypt and decrypt arbitrary values in application code via the
EncryptandDecryptAPIs - Generate data keys for client-side encryption when your application controls the encryption loop
- Enforce compliance requirements that mandate FIPS 140-2 validated key storage
- Share encrypted data across AWS accounts using cross-account key policies
Create a CMK and an Alias
# Create a customer managed key (CMK)
aws kms create-key \
--description "MyApp production encryption key" \
--key-usage ENCRYPT_DECRYPT \
--query 'KeyMetadata.{KeyId:KeyId,Arn:Arn}' \
--output json
# Create a human-readable alias for the key
aws kms create-alias \
--alias-name alias/myapp-prod \
--target-key-id 1a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6dExpected Output:
{
"KeyId": "1a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"Arn": "arn:aws:kms:us-east-1:123456789012:key/1a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
}Encrypt and Decrypt with boto3
import boto3, base64
kms = boto3.client("kms", region_name="us-east-1")
KEY_ID = "alias/myapp-prod"
# Encrypt a plaintext value
plaintext = "super-secret-value"
response = kms.encrypt(
KeyId=KEY_ID,
Plaintext=plaintext.encode(),
)
ciphertext_blob = response["CiphertextBlob"]
encoded = base64.b64encode(ciphertext_blob).decode()
print("Encrypted:", encoded[:40], "...")
# Decrypt - KMS infers the key from the ciphertext metadata
response = kms.decrypt(CiphertextBlob=ciphertext_blob)
recovered = response["Plaintext"].decode()
print("Decrypted:", recovered)Expected Output:
Encrypted: AQICAHh5...base64... ... Decrypted: super-secret-value
Upload to S3 with SSE-KMS
import boto3
s3 = boto3.client("s3")
KEY_ARN = "arn:aws:kms:us-east-1:123456789012:key/1a2b3c4d-..."
# Upload an object with server-side encryption using your CMK
s3.put_object(
Bucket="my-secure-bucket",
Key="sensitive/report.json",
Body=b'{"revenue": 1234567}',
ServerSideEncryption="aws:kms",
SSEKMSKeyId=KEY_ARN,
)
# Bucket-level default encryption (applied to all new objects)
s3.put_bucket_encryption(
Bucket="my-secure-bucket",
ServerSideEncryptionConfiguration={
"Rules": [{
"ApplyServerSideEncryptionByDefault": {
"SSEAlgorithm": "aws:kms",
"KMSMasterKeyID": KEY_ARN,
}
}]
},
)Key rotation does not re-encrypt existing ciphertext. When a CMK rotates, AWS retains all previous key material and can still decrypt data encrypted with older versions. Only new Encrypt calls use the newest key material. This means rotation is always safe to enable.
2. SSM Parameter Store
SSM Parameter Store is a hierarchical key-value store for application configuration and secrets. It organizes values in a path-based hierarchy such as /myapp/prod/db_host, which makes it easy to fetch an entire environment's config in a single API call and to enforce IAM access at the path level rather than per-parameter.
Purpose
Parameter Store bridges the gap between app configuration and secrets. Standard parameters (up to 4 KB) are free and work for plaintext config values. SecureString parameters encrypt the value with a KMS key at rest, making Parameter Store suitable for passwords, tokens, and connection strings that do not require automatic rotation.
Parameter Types
String
Plaintext value. Good for config flags, URLs, region names, and non-sensitive settings.
StringList
Comma-separated list. Useful for allow-lists or multi-value configs stored as a single parameter.
SecureString
KMS-encrypted at rest. Retrieved with WithDecryption=True. Use for passwords, API keys, and connection strings.
Standard vs Advanced
| Feature | Standard | Advanced |
|---|---|---|
| Cost | Free | $0.05 per 10k API calls |
| Max value size | 4 KB | 8 KB |
| Parameter policies | No | Yes - expiration and notification policies |
| Higher throughput | No (40 TPS) | Yes (1,000 TPS) |
When to Use SSM Parameter Store
- Application config that varies by environment, stored under a path like
/myapp/prod/,/myapp/staging/ - Secrets that do not require automatic rotation: third-party API keys, static tokens, and connection strings
- Injecting config into ECS task definitions or Lambda - both support native SSM parameter references, no code needed
- Cost-sensitive workloads where the free Standard tier covers your parameter count
Store Parameters
# Store a plaintext config value (free, Standard tier) aws ssm put-parameter \ --name "/myapp/prod/log_level" \ --value "INFO" \ --type String # Store an encrypted secret (KMS-backed SecureString) aws ssm put-parameter \ --name "/myapp/prod/db_password" \ --value "hunter2" \ --type SecureString \ --key-id "alias/myapp-prod" # Overwrite an existing parameter with a new value aws ssm put-parameter \ --name "/myapp/prod/db_password" \ --value "newpassword123" \ --type SecureString \ --key-id "alias/myapp-prod" \ --overwrite
Retrieve Parameters in Python
import boto3
ssm = boto3.client("ssm", region_name="us-east-1")
# Fetch a single SecureString parameter (decrypted)
response = ssm.get_parameter(
Name="/myapp/prod/db_password",
WithDecryption=True,
)
password = response["Parameter"]["Value"]
print(password)
# Fetch every parameter under /myapp/prod/ in one call
response = ssm.get_parameters_by_path(
Path="/myapp/prod/",
WithDecryption=True,
Recursive=True,
)
config = {
p["Name"].split("/")[-1]: p["Value"]
for p in response["Parameters"]
}
print(config)Expected Output:
hunter2
{'db_password': 'hunter2', 'log_level': 'INFO'}IAM path policies: Grant ssm:GetParametersByPath on the ARN arn:aws:ssm:*:*:parameter/myapp/prod/* to restrict a Lambda to only its own environment's parameters. No per-parameter grants needed.
3. AWS Secrets Manager
Secrets Manager is purpose-built for credential lifecycle management. It stores secrets as JSON blobs, encrypts them with KMS, and can automatically rotate credentials on a schedule by invoking a Lambda function that talks to the backing service (RDS, Redshift, DocumentDB, etc.) and updates the secret without requiring a redeployment of your application.
Purpose
Secrets Manager manages the full lifecycle of a secret: creation, distribution, rotation, and deletion. Applications always call GetSecretValue at runtime rather than baking credentials into config files. When rotation runs, the next call automatically gets the new value with no code change or redeploy required.
Automatic Rotation Flow
Rotation is a four-step Lambda lifecycle that Secrets Manager orchestrates. AWS provides built-in rotation Lambda templates for RDS MySQL, PostgreSQL, Redshift, and DocumentDB. You can write a custom Lambda for any other service following the same four-step interface.
Secrets Manager Rotation Lifecycle
Clients always call GetSecretValue which returns AWSCURRENT. The rollback version is kept as AWSPREVIOUS.
Secret Versioning Stages
AWSCURRENT- the active version returned byGetSecretValueby defaultAWSPENDING- the new version being tested during rotation, not yet promotedAWSPREVIOUS- the last known good version, kept as a rollback option
When to Use Secrets Manager
- Database credentials (RDS, Redshift, DocumentDB) that must rotate automatically without downtime
- Third-party API keys or OAuth tokens that compliance policies require you to rotate on a schedule
- Secrets shared across AWS accounts via resource-based policies (simpler than SSM cross-account)
- Multi-region applications where secrets must be replicated to multiple regions automatically
- Scenarios requiring PCI-DSS, SOC 2, or HIPAA auditable secret rotation records
Create a Secret
# Create a JSON secret for an RDS database
aws secretsmanager create-secret \
--name "myapp/prod/rds-creds" \
--description "RDS master credentials" \
--secret-string '{
"username": "admin",
"password": "hunter2",
"host": "db.cluster-abc.us-east-1.rds.amazonaws.com",
"port": 5432,
"dbname": "myapp"
}'Retrieve and Use a Secret in Python
import boto3, json
sm = boto3.client("secretsmanager", region_name="us-east-1")
# Retrieve the current secret value
response = sm.get_secret_value(SecretId="myapp/prod/rds-creds")
secret = json.loads(response["SecretString"])
# Use directly in your database connection
import psycopg2
conn = psycopg2.connect(
host=secret["host"],
port=secret["port"],
dbname=secret["dbname"],
user=secret["username"],
password=secret["password"],
)
print("Connected to:", secret["host"])Enable Automatic Rotation
# Enable automatic rotation every 30 days
# For RDS secrets, pass --rotation-lambda-arn with the ARN of the
# rotation Lambda AWS created when you set up the secret via the console,
# or use managed rotation (supported for RDS clusters and single-instance RDS).
aws secretsmanager rotate-secret \
--secret-id "myapp/prod/rds-creds" \
--rotation-lambda-arn "arn:aws:lambda:us-east-1:123456789012:function:SecretsManagerMyAppRDSRotation" \
--rotation-rules '{"AutomaticallyAfterDays": 30}' \
--rotate-immediately
# Check the current rotation status
aws secretsmanager describe-secret \
--secret-id "myapp/prod/rds-creds" \
--query 'RotationEnabled'Expected Output:
true
4. SSM Parameter Store vs Secrets Manager
Both services store KMS-encrypted secrets and integrate with IAM. The right choice depends on whether you need automatic rotation and how many secrets you manage.
| Dimension | SSM Parameter Store | Secrets Manager |
|---|---|---|
| Cost | Free (Standard) / $0.05 per 10k calls (Advanced) | $0.40/secret/month + $0.05 per 10k API calls |
| Automatic rotation | No - requires a custom Lambda and a CloudWatch schedule rule | Yes - built-in templates for RDS, Redshift, DocumentDB |
| Max value size | 4 KB (Standard) / 8 KB (Advanced) | 64 KB |
| Versioning | Up to 100 versions (both Standard and Advanced) | Full staging: AWSCURRENT, AWSPENDING, AWSPREVIOUS |
| Cross-account access | Via IAM resource policies | Native resource-based policy, simpler to configure |
| Multi-region replication | No | Yes - replicate to multiple regions with one API call |
| Native service integrations | ECS task definitions, Lambda env vars, CloudFormation | ECS, Lambda, RDS rotation templates, CloudFormation, CDK |
| Best for | App config, feature flags, non-rotating secrets | DB credentials and API keys requiring auto-rotation |
Use SSM Parameter Store when...
- Storing app config alongside secrets in a unified hierarchical store
- The secret does not need to rotate automatically
- Cost matters and you have many parameters (Standard tier is free)
- You want path-based IAM for clean environment isolation
Use Secrets Manager when...
- Storing database credentials that must rotate automatically
- Compliance (PCI-DSS, SOC 2) requires auditable rotation history
- Secrets must be replicated across multiple AWS regions
- Sharing secrets across AWS accounts is required
Both services use KMS underneath. Whether you choose Parameter Store or Secrets Manager, the encryption at rest is provided by KMS. A SecureString parameter and a Secrets Manager secret are both KMS-encrypted blobs. KMS is not optional when you need compliance.
Key Takeaways
- KMS is the encryption backbone - every SecureString parameter and every Secrets Manager secret is KMS-encrypted under the hood. KMS is often invisible, always present.
- Envelope encryption - KMS never encrypts large payloads directly. It encrypts a short-lived data key, which encrypts the actual data. This keeps KMS calls fast and the payload size unlimited.
- Key rotation does not re-encrypt ciphertext - AWS retains old key material so existing data can always be decrypted. New encryptions use the latest material. Rotation is always safe to enable.
- Parameter Store is free for standard parameters - use it for app config, feature flags, and non-rotating secrets. Organize by path hierarchy for clean environment separation.
- Secrets Manager is purpose-built for rotation - its four-step Lambda lifecycle (createSecret, setSecret, testSecret, finishSecret) rotates credentials without downtime or redeployment.
- Never hardcode credentials - always call SSM or Secrets Manager at runtime. Lambda and ECS support native SSM parameter references in their configuration, so no code changes are needed for config values.
- Decision rule - if the secret rotates automatically or needs cross-region replication, use Secrets Manager. For everything else, Parameter Store is cheaper and sufficient.