Advanced ExkPasswd configuration
Mix.install([
{:exk_passwd, "~> 0.2.0"}
])
Case and length
for mode <- [:none, :lower, :upper, :capitalize, :alternate, :random, :invert] do
config = ExkPasswd.Config.new!(num_words: 3, word_length: 5..6, case_transform: mode, separator: "-")
{mode, ExkPasswd.generate(config)}
end
word_length is an ascending range. Non-Latin dictionaries can opt out of the
default 4–10 range by declaring explicit bounds:
ExkPasswd.Config.new!(word_length: 2..4, word_length_bounds: 1..10)
Padding is a minimum, never a truncation rule
config =
ExkPasswd.Config.new!(
num_words: 2,
digits: {0, 0},
separator: "-",
padding: %{char: "=", before: 0, after: 0, to_length: 32}
)
password = ExkPasswd.generate(config)
{password, String.length(password)}
If the natural password is already longer than 32 characters, it is returned unchanged. Choose shorter words or fewer components for a destination with a hard maximum.
Top-level substitutions
config =
ExkPasswd.Config.new!(
num_words: 3,
case_transform: :lower,
substitutions: %{"a" => "@", "e" => "3", "i" => "1", "o" => "0"},
substitution_mode: :always
)
ExkPasswd.generate(config)
Use :random to choose the original or substituted form independently per
word. The entropy model credits that choice only when the forms differ.
Custom dictionaries
words = ["café", "naïve", "résumé", "zürich", "façade"]
ExkPasswd.Dictionary.load_custom(:accented, words)
config =
ExkPasswd.Config.new!(
dictionary: :accented,
word_length: 4..6,
num_words: 3,
case_transform: :lower,
separator: "."
)
ExkPasswd.generate(config)
Custom words are normalized to NFC and must be unique afterward. Loading a
dictionary writes to :persistent_term, so do it at application startup rather
than for each request.
Protocol transforms
defmodule BracketTransform do
defstruct []
defimpl ExkPasswd.Transform do
def apply(_, word, _config), do: "[" <> word <> "]"
def entropy_bits(_, _config), do: 0.0
end
end
config =
ExkPasswd.Config.new!(
num_words: 2,
meta: %{transforms: [%BracketTransform{}]}
)
ExkPasswd.generate(config)
A deterministic transform adds no randomness and may collapse distinct source words to the same output. Built-in Pinyin, Romaji, case, and substitution collisions are included in the seen min-entropy estimate. Random custom-transform callbacks are not trusted as security evidence and receive no rating credit.
Validate a custom output space
password = ExkPasswd.generate(config)
details = ExkPasswd.Entropy.calculate_seen_detailed(config)
%{
password: password,
total: Float.round(details.total, 2),
words: Float.round(details.word_entropy, 2),
transforms: Float.round(details.transform_entropy, 2),
digits: Float.round(details.digit_entropy, 2)
}
Runtime presets
Runtime registration requires ExkPasswd.Config.Presets in your supervision
tree. Built-in presets do not.
case ExkPasswd.Config.Presets.start_link(nil) do
{:ok, _pid} -> :ok
{:error, {:already_started, _pid}} -> :ok
end
custom = ExkPasswd.Config.new!(num_words: 5, separator: "_")
:ok = ExkPasswd.Config.Presets.register(:my_passphrase, custom)
ExkPasswd.generate(:my_passphrase)