Security model

View Source

ExkPasswd 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-down
  • felt-tip
  • t-shirt
  • yo-yo

The resulting file contains 7,772 lowercase ASCII words. Its SHA-256 digest is:

18586c092f641ecd1a471dd6ab35618ab69f0aa7483486424f7caf0996d06259

Verify 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.

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.