KMS is an Elixir toolkit for application-managed encryption.
It gives applications clean interfaces for common encryption scenarios:
- direct symmetric encryption primitives;
- field-level encryption in Phoenix/Ecto apps;
- embedded KMS key storage with SQLite by default;
- a standalone/remote KMS authority process;
- multi-user authorization with principals, sessions, factors, and bindings.
KMS is not one mandatory architecture. Choose the smallest mode that matches the security boundary you need.
Start here
- New Phoenix app: Quickstart
- Pattern selection: Use cases
- App-wide encryption without KMS storage: Application-wide encryption
- SQLite/PostgreSQL persistence: Database configuration
- Root key providers: Root master keys
- AAD design: Additional authenticated data
- Security posture: Security
- Install/config/API: Installation, Configuration, Elixir API, HTTP API
Core concepts
Crypto primitives
KMS.Crypto provides small AES-GCM helpers for encryption, decryption, random keys, and derived keys.
Use primitives directly when the application itself may decrypt at any time and you do not need KMS storage, sessions, or authorization.
See Application-wide encryption.
RMK: root master key
The RMK protects persisted KMS secret material.
Default provider:
KMS.RMK.Local: file-backed local root key.
External providers:
- StackIT KMS
- AWS KMS
- Google Cloud KMS
Use local RMK for simple deployments where DB-only disclosure is the main risk. Use external RMK when database backups/storage need a separate key-service or IAM boundary.
See Root master keys.
KEK: key-encryption key
KMS stores and manages KEKs. Application code normally encrypts data through a KEK alias:
{:ok, ciphertext} = KMS.encrypt_alias("secret", "owner:alice", aad: "document:123/body")
{:ok, plaintext} = KMS.decrypt_alias(ciphertext, "owner:alice", aad: "document:123/body")A common pattern is one KEK alias per owner, tenant, document class, or application boundary.
AAD: additional authenticated data
AAD is non-secret context authenticated with ciphertext. Decrypt requires the same AAD used for encrypt.
Good AAD examples:
document:123/body
integration:456/api-token
webhook-endpoint:789/secretAAD prevents ciphertext from being copied to another row/field and decrypted there.
See Additional authenticated data.
Principals, sessions, factors, bindings
Use these when KMS, not the application alone, must enforce who can encrypt/decrypt.
- Principal: user or service identity.
- Session: bearer credential created after authentication.
- Factor: optional MFA step such as TOTP or WebAuthn.
- Binding: permission linking principal to scope/resource.
See Sessions and Authentication factors.
Deployment modes
1. Crypto-only library
Use KMS.Crypto and optionally KMS.RMK.Local directly.
No KMS OTP application. No KMS database. No KMS HTTP API.
Good when:
- one app owns encryption and decryption;
- decrypt-at-any-time by the app is acceptable;
- database-only disclosure is the main risk;
- simple transparent encryption is enough.
Start with Crypto-only library mode or Application-wide encryption.
2. Embedded KMS in a Phoenix app
Start :kms with the Phoenix app and use the local client.
KMS stores its key tables in SQLite by default, while the app can keep PostgreSQL for normal app tables.
Good when:
- the app owns authorization;
- KMS should manage KEKs and RMK wrapping;
- field-level encryption should stay close to Ecto schemas/changesets.
Start with Embedded KMS or the Quickstart.
3. Standalone KMS process
Run a dedicated KMS authority process with SQLite or PostgreSQL persistence.
Good when:
- multiple apps should share one KMS authority;
- app servers should not open the KMS database directly;
- operations want a separate backup/runtime boundary.
See Standalone KMS with SQLite.
4. Remote KMS service
Expose KMS.API over a private network/TLS and use KMS.Client.Remote from application servers.
Good when:
- apps should only call KMS over an API;
- RMK credentials and KMS database access must stay on KMS hosts;
- network/API controls are part of the security boundary.
See Remote KMS service and Security stance.
Picking a pattern
| Requirement | Start with |
|---|---|
| Encrypt one or two app fields in Phoenix | Quickstart |
| App can decrypt secrets whenever needed | Crypto-only library mode or Application-wide encryption |
| One app should run KMS in-process | Embedded KMS |
| Caller must supply high-entropy shared secret | Shared passphrase documents |
| KMS must enforce per-user decrypt permission | Multi-user encryption |
| Dedicated KMS with no PostgreSQL dependency | Standalone KMS with SQLite |
| App servers call a central KMS authority | Remote KMS service |
| Stronger DB-backup separation | External RMK providers |
Security boundaries
KMS helps most when boundaries are clear.
- Database-only compromise: protect with RMK-wrapped KMS secrets and/or app-wide encryption keys outside the database.
- KMS host compromise: local RMK and in-process keys are usually exposed; use external RMK and host hardening to reduce blast radius, not eliminate it.
- Running app compromise: if the app is allowed to decrypt, an attacker controlling that app can usually decrypt too.
- User-level authorization: use principals, sessions, factors, and bindings when KMS must decide who can decrypt.
Do not log plaintext, ciphertext payloads, session secrets, passwords, factor codes, RMK material, or private keys.
See Security and Crash dumps.
Persistence
KMS defaults to SQLite. This keeps development and embedded deployments simple:
export KMS_DATA_DIR=/var/lib/kms
mix ecto.migrate
PostgreSQL remains supported:
export KMS_DATABASE_BACKEND=postgres
export KMS_DATABASE_URL=ecto://postgres:postgres@localhost/kms_prod
mix ecto.migrate