CI Hex.pm HexDocs

Digestif is a password-hashing library with a deliberately small application API and strict resource bounds around stored hashes.

hash = Digestif.hash("correct horse battery staple")

Digestif.verify?("correct horse battery staple", hash)
#=> true

Digestif.needs_rehash?(hash)
#=> false

The default is Argon2id with 32 MiB of memory, two iterations, and one lane:

def deps do
  [
    {:digestif, "~> 0.2"}
  ]
end

Configuration

Select one primary hasher in application configuration:

config :digestif,
  hasher: {Digestif.Argon2id, []}

Set work factors centrally so hashing and verification always share one validated resource budget:

config :digestif,
  hasher: {Digestif.Argon2id, m_cost: 16}

Existing hashes can migrate without a flag that accepts arbitrary algorithms. Declare the exact legacy hashers that may verify:

config :digestif,
  hasher: {Digestif.Argon2id, []},
  legacy_hashers: [
    {Digestif.PBKDF2, iterations: 600_000, max_iterations: 1_000_000},
    {Digestif.Bcrypt, log_rounds: 12, max_cost: 16}
  ]

After a successful verify?/2, needs_rehash?/1 returns true for a legacy hash. Create and persist a new hash with the primary hasher.

Algorithms

Argon2id is included by default. argon2_elixir compiles a native extension, so deployments need a C toolchain when precompiled artifacts are unavailable. PBKDF2 and bcrypt support are optional; add only the backends the application uses:

{:pbkdf2_elixir, "~> 2.3"}
{:bcrypt_elixir, "~> 3.0"}

Configure {Digestif.PBKDF2, options} when PBKDF2 is specifically required. Bcrypt is primarily for migration because it distinguishes only the first 72 password bytes.

Every bundled adapter:

  • validates options before use;
  • bounds encoded-hash length and attacker-controlled verification cost;
  • performs configured dummy work for malformed or over-budget hashes;
  • emits self-describing hashes; and
  • reports when its work factor should be upgraded.

Verification budgets default to the cost used for new hashes. Imported hashes with higher legitimate costs require an explicit larger budget. See each adapter's module documentation for the allowed ranges.

Libraries and multiple policies

The top-level facade uses application configuration for the common single policy case. Authentication libraries and multi-tenant applications should pass explicit {module, options} tuples through Digestif.Hasher:

primary = {Digestif.Argon2id, []}
legacy = [{Digestif.PBKDF2, []}]

case Digestif.Hasher.verify_with_hashers(password, stored_hash, primary, legacy) do
  {:ok, selected} ->
    Digestif.Hasher.needs_rehash?(stored_hash, selected, primary)

  :error ->
    false
end

License

MIT