ExkPasswd is designed with security as the highest priority. This document outlines the security measures, testing methodology, and known considerations.
All randomness uses :crypto.strong_rand_bytes/1 with rejection sampling to eliminate modulo bias:
# Unbiased random integer generation
def integer(max) do
range_size = 0x1_0000_0000 # 2^32
threshold = range_size - rem(range_size, max)
integer_unbiased(max, threshold)
end
defp integer_unbiased(max, threshold) do
value = :crypto.strong_rand_bytes(4) |> :binary.decode_unsigned()
if value < threshold, do: rem(value, max), else: integer_unbiased(max, threshold)
endBenefits:
- Perfectly uniform distribution (no modulo bias)
- Cryptographically secure randomness
- Verified via chi-square statistical tests
- Zero performance impact (rejection rate < 0.001%)
- Uses the EFF Large Wordlist (7,772 words)
- Pre-computed at compile time for O(1) access
- All words verified reachable through statistical testing
- No filtering bias detected
The shipped list (priv/dict/eff_large.txt) is derived from the canonical
EFF Large Wordlist with a single, documented modification: the four
hyphenated entries (drop-down, felt-tip, t-shirt, yo-yo) are removed
so that every word is strictly lowercase a-z (3-9 characters) and cannot
visually collide with separator characters. Removing 4 of 7,776 words
changes per-word entropy by less than 0.001 bits (log2(7772) = 12.92).
- Source: https://www.eff.org/files/2016/07/18/eff_large_wordlist.txt
- Source SHA-256:
addd35536511597a02fa0a9ff1e5284677b8883b83e986e43f15a3db996b903e - Shipped SHA-256:
18586c092f641ecd1a471dd6ab35618ab69f0aa7483486424f7caf0996d06259
To audit, download the source list, strip the dice-roll column, drop the four hyphenated entries, and compare checksums:
curl -sL https://www.eff.org/files/2016/07/18/eff_large_wordlist.txt \
| awk '{print $2}' | grep -E '^[a-z]+$' | shasum -a 256Run the security test suite:
# All security tests
mix test test/exk_passwd/security_test.exs
# Adversarial attack simulations
mix test test/exk_passwd/adversarial_test.exsThe test suite simulates real attack scenarios:
- Statistical bias detection - Chi-square tests for uniformity
- Collision resistance - Birthday attack validation
- State correlation - Batch generation independence
- Dictionary coverage - Complete word space accessibility
- Entropy validation - Collision rate analysis
- Pattern detection - ML-resistant password structure
- Distribution analysis - Word length, digits, case transforms
- Parallel safety - Process independence verification
Test Coverage: 100% with 859 tests (137 doctests + 722 unit tests), all passing.
Theoretical entropy is calculated assuming:
- Perfect uniform random selection
- Known configuration parameters
- EFF Large Wordlist (7,772 words)
For 4-word password: ~52 bits of entropy (2^52 ≈ 4.5 quadrillion combinations)
Public presets (:default, :xkcd, :wifi, etc.) have predictable structure.
Recommendation: Use custom Config for high-security applications:
config = Config.new!(
num_words: 5,
separator: "+",
word_length: 6..9,
case_transform: :random,
digits: {3, 3}
)
password = ExkPasswd.generate(config)- Preset fingerprinting - Passwords generated with public presets can be identified by structure
- Dictionary knowledge - Attackers knowing the EFF wordlist reduces search space
- Configuration leakage - Password structure reveals some configuration parameters
These are inherent to structured passphrases, not implementation bugs.
Mitigation: Users should use custom configurations for sensitive applications.
If you discover a security vulnerability:
- DO NOT open a public GitHub issue
- Email security concerns to the maintainers
- Include clear reproduction steps
- Allow reasonable time for a fix before disclosure
Validates uniform distribution of random values:
chi_square = Enum.reduce(frequencies, 0, fn {_, observed}, acc ->
acc + :math.pow(observed - expected, 2) / expected
end)
# For df degrees of freedom at 99.9% confidence:
critical_value = df + :math.sqrt(2 * df) * 3.29
assert chi_square < critical_valueValidates entropy claims via birthday paradox:
# Generate samples, check collision rate
unique = Enum.uniq(passwords) |> length()
collision_rate = (total - unique) / total
# Should be near 0 for high-entropy passwords
assert collision_rate < 0.001- Use custom configs for sensitive applications (avoid public presets)
- Increase word count for higher security (6+ words recommended)
- Add randomization to separators and padding when possible
- Monitor entropy using
ExkPasswd.Entropy.calculate/2orExkPasswd.Strength.analyze/2
- Never use
:randmodule - always useExkPasswd.Random - Avoid Enum.random/1 - not cryptographically secure
- Run security tests before committing:
mix test test/exk_passwd/security_test.exs - Maintain test coverage 95%+ overall, 100% for crypto code
ExkPasswd follows NIST SP 800-63B recommendations:
- ✅ Minimum 64 bits entropy for high-value passwords (achieved with 5+ words)
- ✅ Cryptographically secure random number generation
- ✅ No weak patterns or predictable structures
- ✅ Resistance to dictionary attacks (large word space)
- ✅ Use of secure random number generator
- ✅ Sufficient entropy for password generation
- ✅ No hardcoded secrets or predictable patterns
- ✅ Regular security testing and validation
For security-related questions or concerns, please contact the maintainers through the project's GitHub repository.
Last Updated: 2026-04-03