Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]

### Added
- Non-AWS encryption examples for local key usage without AWS credentials (#74)
- Raw AES example demonstrating all key sizes (128/192/256-bit) with encryption context
- Raw RSA example with all 5 padding schemes and PEM key loading from environment variables
- Multi-keyring local example showing key redundancy and rotation patterns
- API Stability Policy guide documenting semantic versioning and breaking change policy (#72)
- Comprehensive module grouping in Hex docs for all keyrings, CMMs, caching, and streaming modules (#72)
- User guides for Getting Started, Choosing Components, and Security Best Practices (#73)
Expand All @@ -17,6 +21,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Consolidated CHANGELOG entries to improve readability and scannability (#81)
- Enhanced streaming module documentation with usage guidance, memory efficiency details, and verification handling (#72)

### Fixed
- RSA keyring PEM loading to correctly decode keys using `pem_entry_decode` instead of `der_decode` (#74)
- All KMS examples updated to use correct Client API format (map-based return values)

## [0.6.0] - 2026-01-31

### Added
Expand Down
56 changes: 54 additions & 2 deletions examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,37 @@

Example scripts demonstrating various encryption scenarios.

## Prerequisites
## Quick Start (No AWS Required)

These examples work without AWS credentials:

```bash
# Basic AES encryption
mix run examples/raw_aes_basic.exs

# RSA encryption with all padding schemes (generates keys)
mix run examples/raw_rsa.exs

# RSA with existing PEM keys
export RSA_PRIVATE_KEY_PEM="$(cat private.pem)"
export RSA_PUBLIC_KEY_PEM="$(cat public.pem)"
mix run examples/raw_rsa.exs

# Multi-keyring for redundancy
mix run examples/multi_keyring_local.exs
```

## AWS KMS Examples

These examples require AWS credentials and KMS keys:

### Prerequisites

1. AWS credentials configured (environment variables, instance profile, or ~/.aws/credentials)
2. KMS key(s) with appropriate permissions
3. Dependencies installed: `mix deps.get`

## Running Examples
### Running KMS Examples

```bash
# Set your KMS key ARN
Expand All @@ -20,9 +44,37 @@ mix run examples/kms_basic.exs

## Examples

### Local Key Examples (No AWS Required)

| File | Description |
|------|-------------|
| `raw_aes_basic.exs` | AES-GCM encryption with local key, all key sizes |
| `raw_rsa.exs` | RSA encryption, all padding schemes, env var PEM support |
| `multi_keyring_local.exs` | Multi-keyring for redundancy and key rotation |

### AWS KMS Examples

| File | Description |
|------|-------------|
| `kms_basic.exs` | Basic encryption/decryption with KMS keyring |
| `kms_discovery.exs` | Discovery keyring for flexible decryption |
| `kms_multi_keyring.exs` | Multi-keyring with KMS generator |
| `kms_cross_region.exs` | Cross-region decryption with MRK keyrings |

## Environment Variables

### RSA Example

| Variable | Description |
|----------|-------------|
| `RSA_PRIVATE_KEY_PEM` | PEM-encoded RSA private key (optional) |
| `RSA_PUBLIC_KEY_PEM` | PEM-encoded RSA public key (optional) |

If both are set, the example uses these keys. If neither is set, keys are generated.

## Security Notes

- **Never hardcode keys** in production code
- **Protect private keys** with appropriate file permissions
- **Use a key management system** for production deployments
- The local key examples are for development and testing
12 changes: 6 additions & 6 deletions examples/kms_basic.exs
Original file line number Diff line number Diff line change
Expand Up @@ -48,20 +48,20 @@ IO.puts("Encryption context: #{inspect(encryption_context)}")

# Encrypt
IO.puts("\nEncrypting...")
{:ok, ciphertext} = Client.encrypt(client, plaintext,
{:ok, result} = Client.encrypt(client, plaintext,
encryption_context: encryption_context
)
IO.puts("Encrypted! Ciphertext size: #{byte_size(ciphertext)} bytes")
IO.puts("Encrypted! Ciphertext size: #{byte_size(result.ciphertext)} bytes")

# Decrypt
IO.puts("\nDecrypting...")
{:ok, {decrypted, returned_context}} = Client.decrypt(client, ciphertext)
{:ok, decrypt_result} = Client.decrypt(client, result.ciphertext)

IO.puts("Decrypted: #{decrypted}")
IO.puts("Returned context: #{inspect(returned_context)}")
IO.puts("Decrypted: #{decrypt_result.plaintext}")
IO.puts("Returned context: #{inspect(decrypt_result.encryption_context)}")

# Verify
if decrypted == plaintext do
if decrypt_result.plaintext == plaintext do
IO.puts("\n✓ Success! Round-trip encryption/decryption verified.")
else
IO.puts("\n✗ Error: Decrypted data doesn't match original!")
Expand Down
10 changes: 5 additions & 5 deletions examples/kms_cross_region.exs
Original file line number Diff line number Diff line change
Expand Up @@ -51,10 +51,10 @@ encrypt_client = Client.new(Default.new(primary_keyring))
plaintext = "Data encrypted in #{primary_region}, to be decrypted in #{replica_region}"

IO.puts("\nEncrypting in #{primary_region}...")
{:ok, ciphertext} = Client.encrypt(encrypt_client, plaintext,
{:ok, result} = Client.encrypt(encrypt_client, plaintext,
encryption_context: %{"source_region" => primary_region}
)
IO.puts("Encrypted! Size: #{byte_size(ciphertext)} bytes")
IO.puts("Encrypted! Size: #{byte_size(result.ciphertext)} bytes")

# ============================================================
# Step 2: Decrypt in replica region
Expand All @@ -67,10 +67,10 @@ IO.puts("\nDecrypting in #{replica_region} using MRK replica...")

decrypt_client = Client.new(Default.new(replica_keyring))

{:ok, {decrypted, context}} = Client.decrypt(decrypt_client, ciphertext)
{:ok, decrypt_result} = Client.decrypt(decrypt_client, result.ciphertext)

IO.puts("Decrypted: #{decrypted}")
IO.puts("Context shows source: #{context["source_region"]}")
IO.puts("Decrypted: #{decrypt_result.plaintext}")
IO.puts("Context shows source: #{decrypt_result.encryption_context["source_region"]}")

IO.puts("\n✓ Cross-region decryption successful!")
IO.puts("Data encrypted in #{primary_region} was decrypted in #{replica_region}")
8 changes: 4 additions & 4 deletions examples/kms_discovery.exs
Original file line number Diff line number Diff line change
Expand Up @@ -49,10 +49,10 @@ encrypt_client = Client.new(Default.new(encrypt_keyring))
plaintext = "Secret message for discovery example"

IO.puts("\nEncrypting with known key...")
{:ok, ciphertext} = Client.encrypt(encrypt_client, plaintext,
{:ok, result} = Client.encrypt(encrypt_client, plaintext,
encryption_context: %{"example" => "discovery"}
)
IO.puts("Encrypted! Size: #{byte_size(ciphertext)} bytes")
IO.puts("Encrypted! Size: #{byte_size(result.ciphertext)} bytes")

# ============================================================
# Step 2: Decrypt with discovery keyring
Expand All @@ -71,8 +71,8 @@ decrypt_client = Client.new(Default.new(discovery_keyring))
IO.puts("\nDecrypting with discovery keyring...")
IO.puts("(Discovery keyring doesn't know which key was used)")

{:ok, {decrypted, _context}} = Client.decrypt(decrypt_client, ciphertext)
IO.puts("Decrypted: #{decrypted}")
{:ok, decrypt_result} = Client.decrypt(decrypt_client, result.ciphertext)
IO.puts("Decrypted: #{decrypt_result.plaintext}")

IO.puts("\n✓ Discovery decryption successful!")
IO.puts("The discovery keyring found the correct key automatically.")
10 changes: 5 additions & 5 deletions examples/kms_multi_keyring.exs
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ encrypt_client = Client.new(Default.new(multi_keyring))
plaintext = "Critical data protected by multiple keys"

IO.puts("\nEncrypting with multi-keyring...")
{:ok, ciphertext} = Client.encrypt(encrypt_client, plaintext)
{:ok, result} = Client.encrypt(encrypt_client, plaintext)
IO.puts("Encrypted! Data key wrapped by both keys.")

# ============================================================
Expand All @@ -69,17 +69,17 @@ IO.puts("Encrypted! Data key wrapped by both keys.")

IO.puts("\nDecrypting with primary key only...")
primary_client = Client.new(Default.new(primary_keyring))
{:ok, {decrypted, _}} = Client.decrypt(primary_client, ciphertext)
IO.puts("✓ Decrypted with primary: #{decrypted}")
{:ok, decrypt_result} = Client.decrypt(primary_client, result.ciphertext)
IO.puts("✓ Decrypted with primary: #{decrypt_result.plaintext}")

# ============================================================
# Decrypt with backup key only
# ============================================================

IO.puts("\nDecrypting with backup key only...")
backup_client = Client.new(Default.new(backup_keyring))
{:ok, {decrypted, _}} = Client.decrypt(backup_client, ciphertext)
IO.puts("✓ Decrypted with backup: #{decrypted}")
{:ok, decrypt_result} = Client.decrypt(backup_client, result.ciphertext)
IO.puts("✓ Decrypted with backup: #{decrypt_result.plaintext}")

IO.puts("\n✓ Multi-keyring example complete!")
IO.puts("Data can be decrypted with either key for redundancy.")
Loading