ExAge (ex_age v0.1.0)

Copy Markdown View Source

age file encryption for Elixir, backed by the Rust age crate (the library behind rage) via Rustler.

Output is standard age v1, so files are interchangeable with the age and rage command-line tools and every other conforming implementation.

Keys

  • Identities are secret keys (AGE-SECRET-KEY-1...), or unencrypted OpenSSH ed25519/rsa private keys.
  • Recipients are public keys (age1...), or ssh-ed25519 .../ssh-rsa ... public keys.

Example

iex> {identity, recipient} = ExAge.generate_identity()
iex> {:ok, ciphertext} = ExAge.encrypt("attack at dawn", recipient)
iex> ExAge.decrypt(ciphertext, identity)
{:ok, "attack at dawn"}

All encryption and decryption runs on dirty CPU schedulers, so it never blocks the BEAM's normal schedulers, including the deliberately slow scrypt passphrase key derivation.

Summary

Functions

Returns true if data looks like ASCII-armored age output.

Decrypts an age file with one or more identities.

Decrypts a passphrase-encrypted age file.

Encrypts plaintext to one or more recipients.

Encrypts plaintext with a passphrase, using scrypt.

Generates a new X25519 key pair.

Derives the public recipient for an X25519 identity.

Types

identity()

@type identity() :: String.t()

reason()

@type reason() :: String.t()

recipient()

@type recipient() :: String.t()

Functions

armored?(data)

@spec armored?(binary()) :: boolean()

Returns true if data looks like ASCII-armored age output.

iex> ExAge.armored?("-----BEGIN AGE ENCRYPTED FILE-----\n...")
true

decrypt(ciphertext, identities)

@spec decrypt(iodata(), identity() | [identity()]) ::
  {:ok, binary()} | {:error, reason()}

Decrypts an age file with one or more identities.

Binary and ASCII-armored input are both accepted. Each identity may be a single key or the full contents of an identity file.

decrypt!(ciphertext, identities)

@spec decrypt!(iodata(), identity() | [identity()]) :: binary()

Like decrypt/2, but raises ExAge.Error on failure.

decrypt_with_passphrase(ciphertext, passphrase, opts \\ [])

@spec decrypt_with_passphrase(iodata(), String.t(), keyword()) ::
  {:ok, binary()} | {:error, reason()}

Decrypts a passphrase-encrypted age file.

Options

  • :max_work_factor - the highest scrypt cost as log2(N) to accept. This protects against files crafted to take hours to decrypt. By default age accepts up to about 16 times its local target.

decrypt_with_passphrase!(ciphertext, passphrase, opts \\ [])

@spec decrypt_with_passphrase!(iodata(), String.t(), keyword()) :: binary()

Like decrypt_with_passphrase/3, but raises ExAge.Error on failure.

encrypt(plaintext, recipients, opts \\ [])

@spec encrypt(iodata(), recipient() | [recipient()], keyword()) ::
  {:ok, binary()} | {:error, reason()}

Encrypts plaintext to one or more recipients.

Options

  • :armor - when true, returns PEM-style ASCII-armored text instead of binary. Defaults to false.

encrypt!(plaintext, recipients, opts \\ [])

@spec encrypt!(iodata(), recipient() | [recipient()], keyword()) :: binary()

Like encrypt/3, but raises ExAge.Error on failure.

encrypt_with_passphrase(plaintext, passphrase, opts \\ [])

@spec encrypt_with_passphrase(iodata(), String.t(), keyword()) ::
  {:ok, binary()} | {:error, reason()}

Encrypts plaintext with a passphrase, using scrypt.

Only use this for passphrases chosen by a person. For programmatic use, generate a key pair with generate_identity/0 instead.

Options

  • :armor - return ASCII-armored text. Defaults to false.
  • :work_factor - the scrypt cost as log2(N). By default age picks a value that takes about one second on the current machine.

encrypt_with_passphrase!(plaintext, passphrase, opts \\ [])

@spec encrypt_with_passphrase!(iodata(), String.t(), keyword()) :: binary()

Like encrypt_with_passphrase/3, but raises ExAge.Error on failure.

generate_identity()

@spec generate_identity() :: {identity(), recipient()}

Generates a new X25519 key pair.

Returns {identity, recipient}. Store the identity as a secret. Share the recipient freely.

iex> {"AGE-SECRET-KEY-1" <> _, "age1" <> _} = ExAge.generate_identity()

to_recipient(identity)

@spec to_recipient(identity()) :: {:ok, recipient()} | {:error, reason()}

Derives the public recipient for an X25519 identity.

iex> {identity, recipient} = ExAge.generate_identity()
iex> ExAge.to_recipient(identity) == {:ok, recipient}
true