Encrypted (Encrypted v0.1.0)

Copy Markdown View Source

Authenticated, non-deterministic encryption for binary Ecto fields.

Each field uses AES-256-GCM with a fresh 96-bit IV. The authentication data binds each ciphertext to its table and storage column. Moving a ciphertext to another table or column therefore causes Encrypted.Error during load.

Declare the type directly and make an explicit redaction decision. The secure choice is redact: true:

field :email, Encrypted, redact: true

Configure a runtime key ring and one active write key:

config :ecto_encrypted,
  keys: %{
    1 => {:system, "FIELD_ENCRYPTION_KEY_V1"},
    2 => {:system, "FIELD_ENCRYPTION_KEY_V2"}
  },
  active_key_id: 2

Encryption always uses the active key ID. Decryption selects a configured key by the ID stored in the envelope. nil passes through unchanged.

This package does not make encrypted values queryable. Store an ecto_blind_search index beside the encrypted field when the application needs equality or bounded-prefix queries.

Schema compilation fails when a field omits :redact. An application can set redact: false to allow plaintext in Ecto's derived Inspect output. This option does not control logs that inspect or interpolate plaintext directly.

Summary

Functions

Derives the raw 32-byte key for one table and storage column.

Generates a cryptographically random 32-byte master key as 64 lowercase hexadecimal digits.

Types

key_source()

@type key_source() ::
  :default
  | binary()
  | {:system, binary()}
  | {module(), atom(), list()}
  | (-> binary())

option()

@type option() ::
  {:keys, %{optional(byte()) => key_source()}}
  | {:active_key_id, byte()}
  | {:key_context, {String.Chars.t(), String.Chars.t()}}
  | {:redact, boolean()}

Functions

derive_key(master_key, table, field)

@spec derive_key(binary(), String.Chars.t(), String.Chars.t()) :: binary()

Derives the raw 32-byte key for one table and storage column.

Applications normally let the Ecto type derive this key. This function exists for format verification and controlled migrations.

Example

iex> master_key = String.duplicate("00", 32)
iex> field_key = Encrypted.derive_key(master_key, "users", "email")
iex> byte_size(field_key)
32

generate_key()

@spec generate_key() :: binary()

Generates a cryptographically random 32-byte master key as 64 lowercase hexadecimal digits.

Example

iex> key = Encrypted.generate_key()
iex> byte_size(key)
64
iex> key =~ ~r/\A[0-9a-f]{64}\z/
true