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

ApplicationKMSStorageGenerateDataKey(CMK)plaintext key + encrypted keyencrypt payload with plaintext keystore: [ciphertext + encrypted key]Decrypt(encrypted key)plaintext keydecrypt ciphertext locally

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

KMSKMS CMKS3S3SSE-KMSEBSEBSvolumesRDSRDSat restSMSecrets MgrDDBDynamoDBat restusesusesusesusesuses

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 Encrypt and Decrypt APIs
  • 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-1e2f3a4b5c6d
Expected 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

FeatureStandardAdvanced
CostFree$0.05 per 10k API calls
Max value size4 KB8 KB
Parameter policiesNoYes - expiration and notification policies
Higher throughputNo (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

Secrets ManagerRotation LambdaRDS1. createSecret - stage new password as AWSPENDING2. setSecret - apply new password to serviceALTER USER SET PASSWORD3. testSecret - verify new credentials worktest connection with AWSPENDINGconnection OK4. finishSecret - promote AWSPENDING to AWSCURRENTold version becomes AWSPREVIOUS

Clients always call GetSecretValue which returns AWSCURRENT. The rollback version is kept as AWSPREVIOUS.

Secret Versioning Stages
  • AWSCURRENT - the active version returned by GetSecretValue by default
  • AWSPENDING - the new version being tested during rotation, not yet promoted
  • AWSPREVIOUS - 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.

DimensionSSM Parameter StoreSecrets Manager
CostFree (Standard) / $0.05 per 10k calls (Advanced)$0.40/secret/month + $0.05 per 10k API calls
Automatic rotationNo - requires a custom Lambda and a CloudWatch schedule ruleYes - built-in templates for RDS, Redshift, DocumentDB
Max value size4 KB (Standard) / 8 KB (Advanced)64 KB
VersioningUp to 100 versions (both Standard and Advanced)Full staging: AWSCURRENT, AWSPENDING, AWSPREVIOUS
Cross-account accessVia IAM resource policiesNative resource-based policy, simpler to configure
Multi-region replicationNoYes - replicate to multiple regions with one API call
Native service integrationsECS task definitions, Lambda env vars, CloudFormationECS, Lambda, RDS rotation templates, CloudFormation, CDK
Best forApp config, feature flags, non-rotating secretsDB 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.