Choosing Components
View SourceThis guide helps you select the right keyring and Cryptographic Materials Manager (CMM) for your use case.
Understanding the Architecture
The AWS Encryption SDK uses two key abstractions:
┌─────────────────────────────────────────────────────────────┐
│ Client │
│ (commitment policy, max EDKs) │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ Cryptographic Materials Manager (CMM) │
│ (key caching, required context enforcement) │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ Keyring │
│ (wraps/unwraps data keys using master keys) │
└─────────────────────────────────────────────────────────────┘Keyring: Wraps and unwraps data encryption keys using your master keys. Choose based on where your keys are stored.
CMM: Manages cryptographic materials. The Default CMM is sufficient for most use cases. Use specialized CMMs for caching or context enforcement.
Keyring Selection
Decision Tree
Where are your master keys stored?
│
├─► AWS KMS
│ │
│ ├─► Do you know the key ARN at decrypt time?
│ │ │
│ │ ├─► YES ──► AwsKms Keyring
│ │ │ (Best for most use cases)
│ │ │
│ │ └─► NO ───► AwsKmsDiscovery Keyring
│ │ (Decrypts any KMS-encrypted message)
│ │
│ └─► Do you need multi-region disaster recovery?
│ │
│ ├─► YES ──► AwsKmsMrk or AwsKmsMrkDiscovery Keyring
│ │ (Works with KMS multi-region keys)
│ │
│ └─► NO ───► Standard AwsKms Keyring
│
├─► Local/HSM keys
│ │
│ ├─► Symmetric key (AES)?
│ │ └─► RawAes Keyring
│ │ (256-bit keys recommended)
│ │
│ └─► Asymmetric key (RSA)?
│ └─► RawRsa Keyring
│ (Useful for encrypt-only or decrypt-only scenarios)
│
└─► Multiple keys for redundancy?
└─► Multi Keyring
(Combines multiple keyrings)Keyring Comparison
| Keyring | Use Case | Key Location | Notes |
|---|---|---|---|
AwsKms | Production with AWS | AWS KMS | Recommended for most cases |
AwsKmsDiscovery | Decrypt unknown sources | AWS KMS | Use discovery filter! |
AwsKmsMrk | Multi-region DR | AWS KMS | For MRK keys only |
AwsKmsMrkDiscovery | Multi-region discovery | AWS KMS | Combine with filter |
RawAes | Local/testing | Your app | You manage key storage |
RawRsa | Asymmetric workflows | Your app | Encrypt-only or decrypt-only |
Multi | Redundancy | Multiple | Combines other keyrings |
Keyring Examples
AWS KMS (Recommended for Production)
alias AwsEncryptionSdk.Keyring.AwsKms
alias AwsEncryptionSdk.Keyring.KmsClient.ExAws
{:ok, kms_client} = ExAws.new(region: "us-west-2")
{:ok, keyring} = AwsKms.new(
"arn:aws:kms:us-west-2:123456789012:key/...",
kms_client
)Raw AES (Local Development/Testing)
alias AwsEncryptionSdk.Keyring.RawAes
key = :crypto.strong_rand_bytes(32) # 256-bit key
{:ok, keyring} = RawAes.new("my-namespace", "my-key", key, :aes_256_gcm)Multi-Keyring (Redundancy)
alias AwsEncryptionSdk.Keyring.Multi
# Primary KMS key + backup KMS key
{:ok, primary} = AwsKms.new(primary_arn, kms_client)
{:ok, backup} = AwsKms.new(backup_arn, kms_client)
{:ok, keyring} = Multi.new(generator: primary, children: [backup])CMM Selection
Decision Tree
Do you need special materials handling?
│
├─► No special needs
│ └─► Default CMM
│ (Wraps any keyring, handles materials lifecycle)
│
├─► High-volume encryption (>1000 ops/sec)?
│ └─► Caching CMM
│ (Reduces KMS calls, improves performance)
│
└─► Enforce required encryption context keys?
└─► RequiredEncryptionContext CMM
(Fails if required keys missing)CMM Comparison
| CMM | Use Case | Notes |
|---|---|---|
Default | Standard operations | Use for most cases |
Caching | High-volume workloads | Set max_age, max_messages limits |
RequiredEncryptionContext | Compliance/security | Enforces context key presence |
CMM Examples
Default CMM (Most Common)
alias AwsEncryptionSdk.Cmm.Default
cmm = Default.new(keyring)
client = Client.new(cmm)Caching CMM (High Volume)
alias AwsEncryptionSdk.Cmm.Caching
alias AwsEncryptionSdk.Cache.LocalCache
alias AwsEncryptionSdk.Stream
# Start the cache process
{:ok, cache} = LocalCache.start_link([])
# Wrap the keyring with caching
cmm = Caching.new_with_keyring(keyring, cache,
max_age: 300, # 5 minutes
max_messages: 1000, # Re-key after 1000 messages
max_bytes: 100_000_000 # Re-key after 100 MB of plaintext
)
client = Client.new(cmm)
# One-shot encrypt uses the cache automatically
{:ok, result} = Client.encrypt(client, plaintext)
# Streaming needs the total size upfront to use the cache
ciphertext =
[plaintext]
|> Stream.encrypt(client, plaintext_length: byte_size(plaintext))
|> Enum.to_list()
|> IO.iodata_to_binary()The max_bytes limit is enforced against each request's declared plaintext
length. Client.encrypt/3 declares it automatically. Streaming can only
declare it when you pass :plaintext_length; without that option the limit
would be unenforceable, so the Caching CMM bypasses its cache for that
stream and fetches fresh materials instead. No cache entry ever serves more
than max_bytes bytes, with one deliberate exception: a single message
larger than max_bytes on its own is served once (rather than refetching
forever), and the entry is refreshed on the next request.
Required Encryption Context CMM
alias AwsEncryptionSdk.Cmm.RequiredEncryptionContext
# Require tenant_id in all encryption operations
cmm = RequiredEncryptionContext.new_with_keyring(["tenant_id"], keyring)
client = Client.new(cmm)
# This succeeds
{:ok, _} = Client.encrypt(client, "data",
encryption_context: %{"tenant_id" => "acme"}
)
# This fails - missing required key
{:error, _} = Client.encrypt(client, "data",
encryption_context: %{"other" => "value"}
)Common Configurations
Development/Testing
key = :crypto.strong_rand_bytes(32)
{:ok, keyring} = RawAes.new("test", "key", key, :aes_256_gcm)
cmm = Default.new(keyring)
client = Client.new(cmm)Production with AWS KMS
{:ok, kms_client} = ExAws.new(region: "us-west-2")
{:ok, keyring} = AwsKms.new(kms_key_arn, kms_client)
cmm = Default.new(keyring)
client = Client.new(cmm)High-Volume Production
{:ok, kms_client} = ExAws.new(region: "us-west-2")
{:ok, keyring} = AwsKms.new(kms_key_arn, kms_client)
{:ok, cache} = LocalCache.start_link([])
cmm = Caching.new_with_keyring(keyring, cache, max_age: 300)
client = Client.new(cmm)
# One-shot API uses the cache automatically
{:ok, result} = Client.encrypt(client, plaintext)
# Streaming needs :plaintext_length to use the cache
ciphertext =
[plaintext]
|> Stream.encrypt(client, plaintext_length: byte_size(plaintext))
|> Enum.to_list()
|> IO.iodata_to_binary()Multi-Tenant with Required Context
{:ok, keyring} = AwsKms.new(kms_key_arn, kms_client)
cmm = RequiredEncryptionContext.new_with_keyring(["tenant_id"], keyring)
client = Client.new(cmm)