Security model
View SourceExkPasswd generates passwords from independently selected words and tokens. Its security assumptions are deliberately public: an attacker may know the library, dictionary, preset, and every configuration value except the random choices.
Randomness
All random choices ultimately use :crypto.strong_rand_bytes/1. Integer ranges
use rejection sampling, including buffered batch generation, so reducing a byte
value modulo a non-power-of-two range does not bias some outcomes.
The generator never uses :rand, Enum.random/1, timestamps, process IDs, or a
user-provided seed for password material.
This guarantees use of the Erlang/OTP cryptographic random source and unbiased range reduction. It does not guarantee that a host with a compromised operating system, runtime, or hardware random source remains secure.
Bundled dictionary
priv/dict/eff_large.txt is derived from the EFF Large Wordlist. Four entries
containing hyphens are omitted so a hyphen can be used as a separator without
making word boundaries ambiguous:
drop-downfelt-tipt-shirtyo-yo
The resulting file contains 7,772 lowercase ASCII words. Its SHA-256 digest is:
18586c092f641ecd1a471dd6ab35618ab69f0aa7483486424f7caf0996d06259Verify it with:
shasum -a 256 priv/dict/eff_large.txt
Selecting from all 7,772 entries would provide log2(7772) ≈ 12.92 bits per
word. Presets restrict word length, so their effective pool can be smaller; use
ExkPasswd.Entropy.calculate_seen/1 for a configuration-specific estimate.
The bundled list is convenient provisioning. Applications may load any suitable
dictionary with ExkPasswd.Dictionary.load_custom/2. Custom words are normalized
to NFC, duplicates are rejected, and case-output duplicates are stored once.
Dictionary authors remain responsible for language coverage, offensive content,
memorability, and resource usage.
Entropy calculation
seen entropy assumes the attacker knows the generator. It is calculated as
min-entropy over reachable outputs, not by adding complexity points. The model:
- counts only dictionary entries in the configured length range;
- accounts for collisions caused by case conversion and substitutions;
- accounts for deterministic Pinyin and Romaji collisions;
- credits random casing or substitution only when it creates distinct outputs;
- gives unverifiable random custom transforms no entropy credit;
- treats digits, the selected separator, and fixed symbol padding as independent choices where the implementation actually makes such choices.
Minimum-length padding receives no entropy credit because some generated values may already meet the minimum and receive no padding. This is conservative.
blind is a character-class brute-force search-space heuristic. It is useful for
contrasting naive brute force with a generator-aware attack, but it is not the
entropy of an observed string.
Crack-time text assumes one billion guesses per second and an average search of half the candidate space. Actual rates vary enormously with online throttling, MFA, password hashing, attacker hardware, and breach conditions.
Transform caveats
Romanization is often many-to-one. For example, the supplied toneless Pinyin
mapping converts both 是 and 事 to shi. A deterministic transform therefore
adds no randomness and can reduce the output space. The entropy calculator
enumerates built-in deterministic outputs to capture those collisions.
Pinyin is character-based and does not disambiguate context-dependent readings. Romaji supports kana, not Kanji morphological analysis. Unmapped characters pass through unchanged. Validate every custom language dictionary if the destination requires ASCII:
transform = %ExkPasswd.Transform.Pinyin{}
unmapped =
Enum.filter(words, fn word ->
ExkPasswd.Transform.apply(transform, word, nil) =~ ~r/[^a-z]/
end)Output length
padding.to_length sets a minimum. ExkPasswd never truncates a generated
password to meet a smaller limit, because truncation can discard entire random
components and invalidate entropy estimates. Choose a configuration whose
maximum natural length fits the destination instead.
The web16, web32, and wifi presets are constructed so their natural maxima
fit their advertised limits. The Wi-Fi preset emits 63 printable ASCII
characters, the passphrase maximum accepted by WPA/WPA2-Personal tooling. A
64-character hexadecimal value represents a raw PSK rather than a 64-character
passphrase.
What this library does not solve
Password generation is only one part of authentication security. ExkPasswd does not:
- check generated or user-supplied values against breach blocklists;
- store, hash, transmit, rotate, or synchronize passwords;
- protect against phishing, keylogging, clipboard monitoring, or endpoint compromise;
- provide rate limiting or multi-factor authentication;
- assess whether a destination silently normalizes or truncates input.
Use a modern password manager and a memory-hard password hash where applicable. Enable MFA for important accounts. Test the exact password field before relying on Unicode, whitespace, or uncommon punctuation.
Current guidance
NIST SP 800-63B emphasizes password length, blocklists, rate limiting, and allowing long passphrases. It advises verifiers not to impose composition rules on user-chosen passwords. OWASP likewise recommends long-password support and explicitly says not to silently truncate passwords.
These are verifier guidelines, not entropy thresholds for this library. The rating bands returned by ExkPasswd are project-defined convenience labels.
- NIST SP 800-63B
- OWASP Authentication Cheat Sheet
- OWASP Password Storage Cheat Sheet
- EFF random-passphrase wordlists
Verification
Run the security-focused tests and normal quality gates with:
mix test test/exk_passwd/security_test.exs test/exk_passwd/adversarial_test.exs
mix test
mix coveralls.html
mix credo --strict
mix dialyzer
mix deps.audit
Statistical tests are regression smoke tests. Passing them does not prove that a
random-number generator is cryptographically secure; that property comes from
the reviewed use of :crypto.strong_rand_bytes/1 and unbiased sampling.
Reporting a vulnerability
Please use GitHub's private security-advisory flow for the repository. Include a minimal reproducer, affected versions, expected impact, and any suggested mitigation. Avoid publishing exploitable details before a fix is available.