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

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:

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/secret

AAD 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

RequirementStart with
Encrypt one or two app fields in PhoenixQuickstart
App can decrypt secrets whenever neededCrypto-only library mode or Application-wide encryption
One app should run KMS in-processEmbedded KMS
Caller must supply high-entropy shared secretShared passphrase documents
KMS must enforce per-user decrypt permissionMulti-user encryption
Dedicated KMS with no PostgreSQL dependencyStandalone KMS with SQLite
App servers call a central KMS authorityRemote KMS service
Stronger DB-backup separationExternal 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

See Database configuration.

Operational docs