Skip to content

Latest commit

 

History

1 Commit

Folders and files

NameName
Last commit message
Last commit date
 
 

Repository files navigation

AWS KMS & Secrets Manager Hands-On Demos

Self-contained CLI demos for AWS Key Management Service (KMS) and Secrets Manager. Run these in your own AWS account to practice encryption, envelope encryption, post-quantum cryptography, and secrets management.

Companion material for the AWS Security Engineering course. These demos are designed to be run independently -- no infrastructure or CloudFormation required.

Requirements:

  • An AWS account with appropriate permissions (see Permissions section)
  • AWS CLI v2 installed and configured (aws --version should show 2.x)
  • OpenSSL (pre-installed on macOS and most Linux)
  • Python 3 (for JSON parsing in some steps)
  • No infrastructure needed -- just CLI commands

Region: All demos default to ap-southeast-1 (Singapore). To use a different region, set this variable before running any commands:

export AWS_REGION=ap-southeast-1   # Change to your preferred region

Then replace --region ap-southeast-1 with --region $AWS_REGION in each command, or simply omit the --region flag entirely (the CLI will use your AWS_REGION env variable).

Cost: Minimal. KMS keys cost $1/month each. API calls cost $0.03 per 10,000 requests. All demos include cleanup steps to delete keys (7-day pending deletion). Total cost for running all demos: less than $0.10 if you clean up promptly.

Platform notes:

  • macOS: The demos use base64 -D (macOS native). If you installed GNU coreutils via Homebrew, use gbase64 -d instead.
  • Linux: Replace base64 -D with base64 -d throughout.

Demo 1: KMS Encrypt/Decrypt

Direct encryption of small data (< 4KB) using the KMS API. The key never leaves KMS.

Time: ~5 minutes

Setup

Create a KMS key for the demo:

KEY_ID=$(aws kms create-key \
  --description "Demo encryption key" \
  --region ap-southeast-1 \
  --query 'KeyMetadata.KeyId' --output text)
echo "Key ID: $KEY_ID"

# Create an alias for convenience
aws kms create-alias \
  --alias-name alias/demo-key \
  --target-key-id $KEY_ID \
  --region ap-southeast-1

Encrypt

# Create a plaintext message
echo -n "Confidential: salary budget is 2.4M" > /tmp/plaintext-msg.txt

# Encrypt it with KMS
aws kms encrypt \
  --key-id alias/demo-key \
  --plaintext fileb:///tmp/plaintext-msg.txt \
  --region ap-southeast-1 \
  --output text --query CiphertextBlob | base64 -D > /tmp/encrypted.bin

# Look at the encrypted output -- it's unintelligible
xxd /tmp/encrypted.bin | head

Decrypt

# Decrypt it back
aws kms decrypt \
  --ciphertext-blob fileb:///tmp/encrypted.bin \
  --region ap-southeast-1 \
  --output text --query Plaintext | base64 -D

# Output: "Confidential: salary budget is 2.4M"

Key takeaway: The encryption key never left KMS. You sent the data to KMS, it encrypted it, and sent back the ciphertext. This is called direct encryption and works for data up to 4KB.

Cleanup

rm /tmp/plaintext-msg.txt /tmp/encrypted.bin
# schedule-key-deletion requires the key ID (not an alias)
aws kms schedule-key-deletion \
  --key-id $KEY_ID \
  --pending-window-in-days 7 \
  --region ap-southeast-1
aws kms delete-alias --alias-name alias/demo-key --region ap-southeast-1

Demo 2: Envelope Encryption

For data larger than 4KB, you use envelope encryption: KMS generates a data key, you encrypt your data locally with that key, then throw away the plaintext key. This is exactly what S3, EBS, and RDS do behind the scenes.

Time: ~10 minutes

Setup

Create a KMS key (or reuse the one from Demo 1):

KEY_ID=$(aws kms create-key \
  --description "Envelope encryption demo key" \
  --region ap-southeast-1 \
  --query 'KeyMetadata.KeyId' --output text)
echo "Key ID: $KEY_ID"

aws kms create-alias \
  --alias-name alias/demo-envelope-key \
  --target-key-id $KEY_ID \
  --region ap-southeast-1

Step 1: Create sample data

echo "This is AnyCompany's confidential financial report for Q4 2025." > /tmp/plaintext.txt

Step 2: Generate a data key

aws kms generate-data-key \
  --key-id alias/demo-envelope-key \
  --key-spec AES_256 \
  --region ap-southeast-1 \
  --output json > /tmp/datakey.json

The response contains two fields:

  • Plaintext -- the data key in plaintext (base64-encoded)
  • CiphertextBlob -- the same data key encrypted by KMS

Step 3: Extract the keys

python3 -c "
import sys, json, base64
d = json.load(open('/tmp/datakey.json'))
open('/tmp/datakey-plaintext.bin','wb').write(base64.b64decode(d['Plaintext']))
open('/tmp/datakey-encrypted.b64','w').write(d['CiphertextBlob'])
"

Step 4: Encrypt the file with OpenSSL

openssl enc -aes-256-cbc -pbkdf2 \
  -in /tmp/plaintext.txt \
  -out /tmp/encrypted-report.enc \
  -pass file:/tmp/datakey-plaintext.bin

Step 5: Destroy the plaintext data key

This is the critical step. The plaintext key must never be stored.

rm /tmp/datakey-plaintext.bin /tmp/datakey.json
echo "Plaintext key destroyed. Only the encrypted data key remains."

Step 6: See what you'd store

ls -la /tmp/encrypted-report.enc /tmp/datakey-encrypted.b64

You store these two files together: the encrypted data + the encrypted data key.

Step 7: Decrypt

First, ask KMS to decrypt the data key:

base64 -D -i /tmp/datakey-encrypted.b64 -o /tmp/datakey-encrypted.bin

aws kms decrypt \
  --ciphertext-blob fileb:///tmp/datakey-encrypted.bin \
  --region ap-southeast-1 \
  --output text --query Plaintext | base64 -D > /tmp/datakey-plaintext.bin

Then use the recovered key to decrypt the file:

openssl enc -aes-256-cbc -pbkdf2 -d \
  -in /tmp/encrypted-report.enc \
  -out /tmp/decrypted-report.txt \
  -pass file:/tmp/datakey-plaintext.bin

cat /tmp/decrypted-report.txt
# Output: "This is AnyCompany's confidential financial report for Q4 2025."

Key takeaway: This is exactly what every AWS service does when you enable encryption. S3, EBS, RDS -- they all generate a data key, encrypt your data locally, and store the encrypted data key alongside it. The plaintext data key only exists in memory, briefly.

Cleanup

rm /tmp/datakey-plaintext.bin /tmp/datakey-encrypted.b64 /tmp/datakey-encrypted.bin \
   /tmp/encrypted-report.enc /tmp/plaintext.txt /tmp/decrypted-report.txt
# schedule-key-deletion requires the key ID (not an alias)
aws kms schedule-key-deletion \
  --key-id $KEY_ID \
  --pending-window-in-days 7 \
  --region ap-southeast-1
aws kms delete-alias --alias-name alias/demo-envelope-key --region ap-southeast-1

Demo 3: Post-Quantum ML-DSA Signatures

KMS supports post-quantum digital signatures using ML-DSA (FIPS 204), standardized by NIST in August 2024. These signatures are resistant to attacks from future quantum computers.

Time: ~7 minutes

Create a post-quantum signing key

PQC_KEY_ID=$(aws kms create-key \
  --key-spec ML_DSA_87 \
  --key-usage SIGN_VERIFY \
  --description "Post-quantum demo key" \
  --region ap-southeast-1 \
  --query 'KeyMetadata.KeyId' --output text)
echo "PQC Key ID: $PQC_KEY_ID"

Examine the key

aws kms describe-key --key-id $PQC_KEY_ID --region ap-southeast-1 \
  --query 'KeyMetadata.{KeyId:KeyId,KeySpec:KeySpec,KeyUsage:KeyUsage,SigningAlgorithms:SigningAlgorithms}' \
  --output table

Note: ML_DSA_87 provides 256-bit classical security. Other options: ML_DSA_44 (128-bit) and ML_DSA_65 (192-bit).

Sign a message

echo -n "Hello post-quantum world" > /tmp/pqc-message.bin

aws kms sign \
  --key-id $PQC_KEY_ID \
  --message fileb:///tmp/pqc-message.bin \
  --message-type RAW \
  --signing-algorithm ML_DSA_SHAKE_256 \
  --region ap-southeast-1 \
  --output json > /tmp/pqc-signature.json

Verify the signature

# Extract the signature to a binary file
python3 -c "
import json, base64
sig = json.load(open('/tmp/pqc-signature.json'))['Signature']
open('/tmp/pqc-sig.bin','wb').write(base64.b64decode(sig))
"

# Verify
aws kms verify \
  --key-id $PQC_KEY_ID \
  --message fileb:///tmp/pqc-message.bin \
  --message-type RAW \
  --signing-algorithm ML_DSA_SHAKE_256 \
  --signature fileb:///tmp/pqc-sig.bin \
  --region ap-southeast-1 \
  --output table

# Expected: SignatureValid = True

This key can only sign (not encrypt)

echo -n "test" > /tmp/pqc-test.txt
aws kms encrypt --key-id $PQC_KEY_ID \
  --plaintext fileb:///tmp/pqc-test.txt \
  --region ap-southeast-1
# Error: InvalidKeyUsageException -- this key is SIGN_VERIFY only

Key takeaway: Post-quantum signatures are available in KMS today. If you sign firmware, code, or long-lived documents, start planning your PQC migration now. NIST standardized ML-DSA in August 2024.

Cleanup

aws kms schedule-key-deletion \
  --key-id $PQC_KEY_ID \
  --pending-window-in-days 7 \
  --region ap-southeast-1
rm /tmp/pqc-message.bin /tmp/pqc-signature.json /tmp/pqc-sig.bin /tmp/pqc-test.txt 2>/dev/null

Demo 4: Secrets Manager -- Store, Retrieve, and Rotate

Create a secret, retrieve it via CLI, rotate it, and see the new value. This is how applications should handle credentials -- never hardcode them.

Time: ~5 minutes

Store a secret

aws secretsmanager create-secret \
  --name demo/api-credentials \
  --description "Demo API credentials" \
  --secret-string '{"api_key":"sk-demo-abc123","api_secret":"super-secret-value","endpoint":"https://api.example.com"}' \
  --region ap-southeast-1

Retrieve it

# Get the full secret
aws secretsmanager get-secret-value \
  --secret-id demo/api-credentials \
  --region ap-southeast-1 \
  --query 'SecretString' --output text | python3 -m json.tool

This is what your application does at startup or on every request -- fetch credentials from Secrets Manager, never store them on disk.

Rotate it (manual update)

In production, rotation is automated with Lambda. Here we'll simulate it manually:

# Update the secret with a new API key
aws secretsmanager put-secret-value \
  --secret-id demo/api-credentials \
  --secret-string '{"api_key":"sk-demo-xyz789-rotated","api_secret":"new-secret-after-rotation","endpoint":"https://api.example.com"}' \
  --region ap-southeast-1

# Retrieve again -- the new value is returned immediately
aws secretsmanager get-secret-value \
  --secret-id demo/api-credentials \
  --region ap-southeast-1 \
  --query 'SecretString' --output text | python3 -m json.tool

View version history

aws secretsmanager list-secret-version-ids \
  --secret-id demo/api-credentials \
  --region ap-southeast-1 \
  --query 'Versions[*].{VersionId:VersionId,Stages:VersionStages}' \
  --output table

You'll see AWSCURRENT (the active version) and AWSPREVIOUS (the old version). Secrets Manager keeps both so you can roll back if needed.

Key takeaway: Applications fetch secrets at runtime -- no hardcoded credentials, no config files with passwords. When you rotate, the application picks up the new value on the next request. Zero code changes.

Cleanup

aws secretsmanager delete-secret \
  --secret-id demo/api-credentials \
  --force-delete-without-recovery \
  --region ap-southeast-1

Demo 5: S3 Bucket Encryption with KMS

Every S3 bucket is encrypted by default (SSE-S3). But you can upgrade to KMS encryption for audit trails, key rotation control, and access policies tied to the key. This demo shows the difference.

Time: ~5 minutes

Create a KMS key and an encrypted bucket

# Create a KMS key for S3
S3_KEY_ID=$(aws kms create-key \
  --description "S3 encryption demo key" \
  --region ap-southeast-1 \
  --query 'KeyMetadata.KeyId' --output text)
echo "Key ID: $S3_KEY_ID"

aws kms create-alias \
  --alias-name alias/demo-s3-key \
  --target-key-id $S3_KEY_ID \
  --region ap-southeast-1

# Create a bucket with KMS encryption and S3 Bucket Key enabled
BUCKET_NAME="kms-demo-$(aws sts get-caller-identity --query Account --output text)-$(date +%s)"
aws s3api create-bucket \
  --bucket $BUCKET_NAME \
  --region ap-southeast-1 \
  --create-bucket-configuration LocationConstraint=ap-southeast-1

aws s3api put-bucket-encryption \
  --bucket $BUCKET_NAME \
  --server-side-encryption-configuration '{
    "Rules": [{
      "ApplyServerSideEncryptionByDefault": {
        "SSEAlgorithm": "aws:kms",
        "KMSMasterKeyID": "alias/demo-s3-key"
      },
      "BucketKeyEnabled": true
    }]
  }'
echo "Bucket: $BUCKET_NAME"

Upload a file and check its encryption

# Upload a test file
echo "This file is encrypted with my KMS key" > /tmp/s3-demo.txt
aws s3 cp /tmp/s3-demo.txt s3://$BUCKET_NAME/demo.txt --region ap-southeast-1

# Check the encryption -- notice SSEKMSKeyId shows your key
aws s3api head-object \
  --bucket $BUCKET_NAME \
  --key demo.txt \
  --region ap-southeast-1 \
  --query '{Encryption:ServerSideEncryption,KMSKeyId:SSEKMSKeyId,BucketKeyEnabled:BucketKeyEnabled}'

See KMS in CloudTrail

# After ~5 minutes, you can see the KMS Decrypt calls in CloudTrail
# This is the audit trail you get with KMS encryption (not available with SSE-S3)
aws cloudtrail lookup-events \
  --lookup-attributes AttributeKey=EventName,AttributeValue=Decrypt \
  --max-results 5 \
  --region ap-southeast-1 \
  --query 'Events[*].{Time:EventTime,Event:EventName,User:Username}' \
  --output table

Key takeaways:

  • SSE-S3 (default): AWS manages everything. No audit trail of key usage. Free.
  • SSE-KMS: You control the key. Every access is logged in CloudTrail. You can revoke access by disabling the key. $1/month per key + $0.03/10k requests.
  • S3 Bucket Key: Reduces KMS API calls (and cost) by caching a bucket-level data key. Always enable this with SSE-KMS.

Cleanup

aws s3 rm s3://$BUCKET_NAME --recursive
aws s3api delete-bucket --bucket $BUCKET_NAME --region ap-southeast-1
# schedule-key-deletion requires the key ID (not an alias)
aws kms schedule-key-deletion \
  --key-id $S3_KEY_ID \
  --pending-window-in-days 7 \
  --region ap-southeast-1
aws kms delete-alias --alias-name alias/demo-s3-key --region ap-southeast-1
rm /tmp/s3-demo.txt

Permissions

Minimum IAM permissions needed to run all demos:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "KMSDemos",
      "Effect": "Allow",
      "Action": [
        "kms:CreateKey", "kms:CreateAlias", "kms:DeleteAlias",
        "kms:DescribeKey", "kms:ScheduleKeyDeletion",
        "kms:Encrypt", "kms:Decrypt", "kms:GenerateDataKey",
        "kms:Sign", "kms:Verify"
      ],
      "Resource": "*"
    },
    {
      "Sid": "SecretsManagerDemo",
      "Effect": "Allow",
      "Action": [
        "secretsmanager:CreateSecret", "secretsmanager:GetSecretValue",
        "secretsmanager:PutSecretValue", "secretsmanager:ListSecretVersionIds",
        "secretsmanager:DeleteSecret"
      ],
      "Resource": "*"
    },
    {
      "Sid": "S3Demo",
      "Effect": "Allow",
      "Action": [
        "s3:CreateBucket", "s3:DeleteBucket", "s3:PutObject", "s3:GetObject",
        "s3:DeleteObject", "s3:PutEncryptionConfiguration",
        "s3:ListBucket"
      ],
      "Resource": "*"
    },
    {
      "Sid": "CloudTrailLookup",
      "Effect": "Allow",
      "Action": ["cloudtrail:LookupEvents"],
      "Resource": "*"
    },
    {
      "Sid": "STSIdentity",
      "Effect": "Allow",
      "Action": ["sts:GetCallerIdentity"],
      "Resource": "*"
    }
  ]
}

Tip: If you have AdministratorAccess or PowerUserAccess, you already have all required permissions.

Notes

  • Key deletion: KMS keys have a mandatory 7-30 day waiting period before deletion. You will be charged $1/month per key until deletion completes.
  • Cleanup: Always run the cleanup steps after each demo to avoid ongoing charges.
  • Safety: These demos create no infrastructure (no EC2, no VPCs). The only billable resources are KMS keys ($1/month each) and a temporary S3 bucket (Demo 5).

Created as companion material for the AWS Security Engineering course.

About

Public demos for the AWS Security Engineering Course

Resources

Stars

0 stars

Watchers

0 watching

Forks

Contributors