# ExkPasswd
> ExkPasswd generates strong passwords by combining random words with numbers, symbols, and various transformations. This creates passwords that are both cryptographically secure and easier to remember than random character strings.
---
[](https://github.com/futhr/exk_passwd/actions)
[](https://codecov.io/gh/futhr/exk_passwd)
[](https://github.com/futhr/exk_passwd)
[](https://hex.pm/packages/exk_passwd)
[](https://hexdocs.pm/exk_passwd)
[](https://opensource.org/licenses/BSD-2-Clause)
[](https://elixir-lang.org)
---
## Try It Interactively
Explore ExkPasswd with interactive Livebook notebooks:
[](https://livebook.dev/run?url=https%3A%2F%2Fraw.githubusercontent.com%2Ffuthr%2Fexk_passwd%2Fmain%2Fnotebooks%2Fquickstart.livemd)
* **[Quick Start](https://livebook.dev/run?url=https%3A%2F%2Fraw.githubusercontent.com%2Ffuthr%2Fexk_passwd%2Fmain%2Fnotebooks%2Fquickstart.livemd)** - Basic usage and examples
* **[Advanced Usage](https://livebook.dev/run?url=https%3A%2F%2Fraw.githubusercontent.com%2Ffuthr%2Fexk_passwd%2Fmain%2Fnotebooks%2Fadvanced.livemd)** - Custom configurations and transformations
* **[Security Analysis](https://livebook.dev/run?url=https%3A%2F%2Fraw.githubusercontent.com%2Ffuthr%2Fexk_passwd%2Fmain%2Fnotebooks%2Fsecurity.livemd)** - Entropy, strength, and cryptographic properties
* **[Benchmarks](https://livebook.dev/run?url=https%3A%2F%2Fraw.githubusercontent.com%2Ffuthr%2Fexk_passwd%2Fmain%2Fnotebooks%2Fbenchmarks.livemd)** - Performance metrics and comparisons
* **[Chinese i18n](https://livebook.dev/run?url=https%3A%2F%2Fraw.githubusercontent.com%2Ffuthr%2Fexk_passwd%2Fmain%2Fnotebooks%2Fi18n_chinese.livemd)** - 中文密码生成 (Chinese password generation with Pinyin)
* **[Japanese i18n](https://livebook.dev/run?url=https%3A%2F%2Fraw.githubusercontent.com%2Ffuthr%2Fexk_passwd%2Fmain%2Fnotebooks%2Fi18n_japanese.livemd)** - 日本語パスワード生成 (Japanese password generation with Romaji)
---
## History & Inspiration
The concept of using random words for passwords was popularized by [Randall Munroe's XKCD comic #936](https://xkcd.com/936/), which illustrated why long, memorable passphrases can be more effective than short, complex passwords.
<p align="center">
<img src="https://raw.githubusercontent.com/futhr/exk_passwd/main/priv/static/xkcd.png" alt="XKCD Password Strength Comic" width="740">
<br>
<em>XKCD #936: Password Strength - "Through 20 years of effort, we've successfully trained everyone to use passwords that are hard for humans to remember, but easy for computers to guess."</em>
</p>
This comic inspired [Bart Busschots](https://www.bartbusschots.ie/) to create the original Perl module [Crypt::HSXKPasswd](https://github.com/bbusschots/hsxkpasswd), which implements a secure and flexible password generation system based on this principle. The concept was later ported to JavaScript, and subsequently to Elixir by [Michael Westbay](https://github.com/westbaystars).
ExkPasswd builds upon this foundation with a ground-up rewrite in Elixir, enhanced with the [EFF Large Wordlist](https://www.eff.org/deeplinks/2016/07/new-wordlists-random-passphrases) for maximum security and memorability, cryptographically secure random number generation, and modern Elixir features.
---
## Why Word-Based Passwords?
Traditional password advice suggests random strings like `x4$9Kp2m`, but these have problems:
- **Hard to remember** → people write them down (insecure)
- **Hard to type** → increased friction and errors
- **Short to be memorable** → limited entropy
Word-based passwords like `correct-horse-battery-staple` offer:
- **Easy to remember** (no need to write down)
- **Easy to type** (real words)
- **Long enough for high entropy** (more characters = exponentially more secure)
- **Still unpredictable** when generated with cryptographic randomness
---
## Features
### Core Features
- **Cryptographically Secure** - Uses `:crypto.strong_rand_bytes/1` for all randomness
- **EFF Large Wordlist** - 7,826 carefully curated words (12.9 bits entropy per word)
- **Zero Runtime Dependencies** - Only uses Elixir stdlib and `:crypto`
- **Multiple Presets** - 7 built-in presets for different use cases
- **Fully Customizable** - Fine-grained control over all generation parameters
### Advanced Features
- **Efficient Performance** - Tuple-based constant-time word lookups
- **Entropy Analysis** - Blind and seen entropy calculations
- **Strength Feedback** - Password strength reports
- **Character Substitutions** - Leetspeak-style transformations for additional entropy
- **Custom Dictionaries** - Load and use your own word lists via ETS
- **Batch Generation** - Optimized generation of multiple passwords
- **Parallel Generation** - Multi-core support for large batches
- **Pre-computed Transformations** - Cached case transformations for efficiency
---
## Installation
Add `exk_passwd` to your dependencies in `mix.exs`:
```elixir
def deps do
[
{:exk_passwd, "~> 0.1.1"}
]
end
```
Then run:
```bash
mix deps.get
```
---
## Quick Start
### Basic Usage
```elixir
# Generate a password with default settings
ExkPasswd.generate()
#=> "45?clever?FOREST?mountain?89"
# Use a preset
ExkPasswd.generate(:xkcd)
#=> "correct-horse-battery-staple-forest-cloud"
# Use preset as string
ExkPasswd.generate("wifi")
#=> "2847-happy-CLOUD-forest-WINTER-gentle-SUMMER-4839???????????????????"
```
### Custom Configuration
```elixir
# Using keyword list
ExkPasswd.generate(
num_words: 4,
word_length: 5..7,
case_transform: :capitalize,
separator: "-",
digits: {3, 3},
padding: %{char: "!", before: 1, after: 1}
)
#=> "!389-Happy-Forest-Guitar-Cloud-472!"
# Or create a Config struct
config = ExkPasswd.Config.new!(
num_words: 4,
word_length: 5..7,
case_transform: :capitalize,
separator: "-",
digits: {3, 3},
padding: %{char: "!", before: 1, after: 1}
)
ExkPasswd.generate(config)
#=> "!389-Happy-Forest-Guitar-Cloud-472!"
```
---
## Available Presets
### `:default`
Balanced security and memorability. 3 words with alternating case, random separator, 2 digits before/after, and 2 padding characters.
```elixir
ExkPasswd.generate(:default)
#=> "45?clever?FOREST?mountain?89"
```
### `:xkcd`
Similar to the famous XKCD comic. 5 words, lowercase, separated by hyphens, no padding. Great balance of security and memorability.
```elixir
ExkPasswd.generate(:xkcd)
#=> "correct-horse-battery-staple-amazing"
```
### `:web32`
For websites allowing up to 32 characters. 4 words, compact format.
```elixir
ExkPasswd.generate(:web32)
#=> "!29-word-CLOUD-tree-HAPPY-847@"
```
### `:web16`
For websites with 16 character limits. **Not recommended** - too short for good security. Only use if absolutely required.
```elixir
ExkPasswd.generate(:web16)
#=> "word!TREE@word#4"
```
### `:wifi`
63-character WPA2 keys (most routers allow 64, but some only 63).
```elixir
ExkPasswd.generate(:wifi)
#=> "2847-happy-CLOUD-forest-WINTER-gentle-SUMMER-4839???????????????????"
```
### `:apple_id`
Meets Apple ID password requirements. Uses only symbols from iOS keyboard for easy mobile typing.
```elixir
ExkPasswd.generate(:apple_id)
#=> ":45-Word-CLOUD-Forest-89:"
```
### `:security`
For fake security question answers. Natural sentence-like format.
```elixir
ExkPasswd.generate(:security)
#=> "word cloud forest happy guitar mountain."
```
---
## Configuration Options
All configuration is done via the `ExkPasswd.Config` struct, or by passing keyword lists:
```elixir
# Using keyword list (recommended)
ExkPasswd.generate(
num_words: 3, # Number of words (1-10)
word_length: 4..8, # Word length range
case_transform: :alternate, # :none | :alternate | :capitalize | :invert | :lower | :upper | :random
separator: "-", # Separator between words (string or random from charset)
digits: {2, 2}, # {before, after} - digits before/after words (0-5 each)
padding: %{ # Padding configuration
char: "!", # Padding character (string or random from charset)
before: 2, # Padding chars before (0-5)
after: 2, # Padding chars after (0-5)
to_length: 0 # If > 0, pad/truncate to exact length (overrides before/after)
},
dictionary: :eff, # :eff (default) | custom atom for loaded dictionaries
meta: %{ # Metadata and extensions
transforms: [] # Custom Transform protocol implementations
}
)
# Or create Config struct explicitly
config = ExkPasswd.Config.new!(
num_words: 3,
word_length: 4..8,
separator: "-"
)
```
### Case Transformations
- `:none` - No transformation (words as-is from dictionary)
- `:alternate` - Alternating case: `word`, `WORD`, `word`, `WORD`
- `:capitalize` - Capitalize first letter: `Word`, `Word`, `Word`
- `:invert` - Invert case: `wORD` (lowercase first, uppercase rest)
- `:lower` - All lowercase: `word`, `word`, `word`
- `:upper` - All uppercase: `WORD`, `WORD`, `WORD`
- `:random` - Each word randomly uppercase or lowercase
---
## Security
### Cryptographic Randomness
All random operations use `:crypto.strong_rand_bytes/1`, which provides cryptographically secure randomness backed by your operating system's secure random number generator. This ensures passwords are unpredictable and suitable for security-critical applications.
**Never use `Enum.random/1` or the `:rand` module for password generation** - they use predictable pseudo-random number generators.
### Password Strength
Password strength is measured in **bits of entropy**:
- **< 28 bits**: Very weak (avoid)
- **28-35 bits**: Weak (acceptable only for low-value accounts)
- **36-59 bits**: Fair (acceptable for most accounts)
- **60-127 bits**: Strong (recommended for sensitive accounts)
- **128+ bits**: Very strong (suitable for encryption keys)
ExkPasswd's default preset generates passwords with **high entropy** while remaining memorable.
### Dictionary & Security Model
ExkPasswd uses the **EFF Large Wordlist** containing 7,826 carefully curated words:
- **Memorability** - Common, recognizable English words
- **Typability** - No complex spellings or rare words
- **Length variety** - 3-9 characters per word
- **Safety** - No offensive or problematic words
- **High entropy** - 12.9 bits of entropy per word
**Security comes from entropy and cryptographic randomness, not from secret words.**
The EFF wordlist provides exceptional security:
- **4 words**: ~51.6 bits (adequate for most accounts)
- **5 words**: ~64.5 bits (strong)
- **6 words**: ~77.5 bits (very strong - EFF recommendation)
- **7+ words**: ~90+ bits (excellent, suitable for master passwords)
All random selection uses `:crypto.strong_rand_bytes/1` for cryptographic security, ensuring passwords are unpredictable even if the word list is known.
**References:**
- [EFF Large Wordlist](https://www.eff.org/deeplinks/2016/07/new-wordlists-random-passphrases)
- [Original Perl Implementation](https://github.com/bbusschots/hsxkpasswd)
---
## API Reference
### Main Functions
#### `ExkPasswd.generate/0`
Generates a password using default settings.
```elixir
ExkPasswd.generate()
#=> "45?clever?FOREST?mountain?89"
```
#### `ExkPasswd.generate/1`
Generates a password with a preset (atom), keyword list, or Config struct.
```elixir
# With preset atom
ExkPasswd.generate(:xkcd)
#=> "correct-horse-battery-staple-amazing"
# With keyword list (new!)
ExkPasswd.generate(num_words: 2, separator: "_")
#=> "45_HAPPY_forest_23"
# With Config struct
config = ExkPasswd.Config.new!(num_words: 2, separator: "_")
ExkPasswd.generate(config)
#=> "45_HAPPY_forest_23"
```
#### `ExkPasswd.generate/2`
Generates a password with a preset and keyword list overrides.
```elixir
# Start with xkcd preset, override num_words
ExkPasswd.generate(:xkcd, num_words: 7)
#=> "correct-horse-battery-staple-amazing-forest-cloud"
```
#### `ExkPasswd.Config.Presets.all/0`
Returns list of all available preset configurations.
```elixir
ExkPasswd.Config.Presets.all()
#=> [%ExkPasswd.Config{...}, ...]
```
#### `ExkPasswd.Config.Presets.get/1`
Gets a specific preset by name (atom or string).
```elixir
ExkPasswd.Config.Presets.get(:xkcd)
#=> %ExkPasswd.Config{...}
ExkPasswd.Config.Presets.get("wifi")
#=> %ExkPasswd.Config{...}
ExkPasswd.Config.Presets.get(:nonexistent)
#=> nil
```
#### `ExkPasswd.Config.Presets.register/2`
Register a custom preset at runtime.
```elixir
custom = ExkPasswd.Config.new!(num_words: 8, separator: "_")
ExkPasswd.Config.Presets.register(:super_strong, custom)
# Now use it
ExkPasswd.generate(:super_strong)
#=> "45_word_CLOUD_forest_HAPPY_guitar_MOUNTAIN_test_89"
```
### Config Validation
#### `ExkPasswd.Config.new/1`
Creates and validates a Config struct, returns `{:ok, config}` or `{:error, message}`.
```elixir
ExkPasswd.Config.new(num_words: 4)
#=> {:ok, %ExkPasswd.Config{num_words: 4, ...}}
ExkPasswd.Config.new(num_words: 0)
#=> {:error, "num_words must be between 1 and 10, got: 0"}
```
#### `ExkPasswd.Config.new!/1`
Like `new/1` but raises `ArgumentError` on failure.
```elixir
ExkPasswd.Config.new!(num_words: 4)
#=> %ExkPasswd.Config{num_words: 4, ...}
ExkPasswd.Config.new!(num_words: 0)
#=> ** (ArgumentError) num_words must be between 1 and 10, got: 0
```
### Advanced Features
#### Batch Generation
Generate multiple passwords efficiently:
```elixir
# Generate 100 passwords (optimized)
ExkPasswd.generate_batch(100)
#=> ["45?clever?FOREST?...", "23@happy@CLOUD@...", ...]
# Generate unique passwords only
ExkPasswd.generate_unique_batch(50)
#=> Guarantees all 50 passwords are unique
# Parallel generation (uses all CPU cores)
ExkPasswd.generate_parallel(1000)
#=> Fastest for large batches
```
#### Entropy Calculation
Analyze password strength with comprehensive entropy analysis:
```elixir
password = "45?clever?FOREST?mountain?89"
config = ExkPasswd.Config.new!()
# Calculate entropy
ExkPasswd.calculate_entropy(password, config)
#=> %{
# blind: 125.4, # Brute-force resistance in bits
# seen: 72.3, # Knowledge-based attack resistance
# status: :good, # :excellent | :good | :fair | :weak
# blind_crack_time: "5.4 billion years",
# seen_crack_time: "75.2 millennia",
# details: %{...} # Detailed breakdown
# }
```
#### Strength Analysis
Get user-friendly strength feedback:
```elixir
password = "correct-horse-battery-staple"
config = ExkPasswd.Config.new!(num_words: 4)
# Get strength rating
ExkPasswd.strength_rating(password, config)
#=> :good
# Get detailed analysis
ExkPasswd.analyze_strength(password, config)
#=> %{
# rating: :good,
# score: 72, # 0-100 scale
# entropy_bits: 51.6
# }
```
#### Transform Protocol (Extensibility)
ExkPasswd supports custom transformations via the Transform protocol:
```elixir
# Use built-in substitution transform
config = ExkPasswd.Config.new!(
num_words: 3,
meta: %{
transforms: [
%ExkPasswd.Transform.Substitution{
map: %{"a" => "@", "e" => "3", "i" => "!", "o" => "0", "s" => "$"},
mode: :random # Randomly apply per word for extra entropy
}
]
}
)
ExkPasswd.generate(config)
#=> "45?cl3v3r?FOREST?m0unt@!n?89"
# Example 1: Japanese Romaji Transform (Built-in)
# ExkPasswd includes a production-ready Modified Hepburn romanization transform
# with full support for modern Japanese including Katakana loanwords
# Load Japanese dictionary (Hiragana and Katakana)
ExkPasswd.Dictionary.load_custom(:japanese, [
"さくら", "やま", "うみ", "そら", "おちゃ", "きょうと", # Traditional Hiragana
"コーヒー", "ファイル", "ウィンドウ", "パーティー" # Modern Katakana loanwords
])
config = ExkPasswd.Config.new!(
num_words: 3,
dictionary: :japanese,
word_length: 2..8,
word_length_bounds: 1..15,
separator: "-",
meta: %{
transforms: [%ExkPasswd.Transform.Romaji{}]
}
)
ExkPasswd.generate(config)
#=> "45-sakura-koohii-fairu-89" # さくら, コーヒー, ファイル romanized
# Features:
# - Modified Hepburn with Wāpuro IME conventions (きょうと → kyouto, おちゃ → ocha)
# - Sokuon gemination (がっこう → gakkou, まっちゃ → matcha)
# - Palatalization (しゃしん → shashin, ちゃ → cha)
# - N before labials (さんぽ → sampo, しんぶん → shimbun)
# - Long vowel markers (コーヒー → koohii, ラーメン → raamen)
# - Extended Katakana (ファイル → fairu, ウィンドウ → windou, ヴァイオリン → vaiorin)
# - Full Unicode support for Hiragana, Katakana, and extended sounds
# Example 2: NATO Phonetic Alphabet Transform
defmodule MyApp.PhoneticTransform do
@moduledoc """
Converts password words to NATO phonetic alphabet for unambiguous verbal communication.
Useful for passwords communicated over radio, phone, or in high-noise
environments where clarity is critical (aviation, military, emergency response).
"""
defstruct [:format] # :full | :abbreviated
@nato_phonetic %{
"a" => "Alpha", "b" => "Bravo", "c" => "Charlie", "d" => "Delta",
"e" => "Echo", "f" => "Foxtrot", "g" => "Golf", "h" => "Hotel",
"i" => "India", "j" => "Juliet", "k" => "Kilo", "l" => "Lima",
"m" => "Mike", "n" => "November", "o" => "Oscar", "p" => "Papa",
"q" => "Quebec", "r" => "Romeo", "s" => "Sierra", "t" => "Tango",
"u" => "Uniform", "v" => "Victor", "w" => "Whiskey", "x" => "X-ray",
"y" => "Yankee", "z" => "Zulu"
}
defimpl ExkPasswd.Transform do
def apply(%{format: format}, word, _config) do
word
|> String.downcase()
|> String.graphemes()
|> Enum.map(fn char ->
phonetic = Map.get(@nato_phonetic, char, char)
if format == :abbreviated, do: String.slice(phonetic, 0, 3), else: phonetic
end)
|> Enum.join("-")
end
def entropy_bits(%{format: _}, _config) do
# Phonetic transform is deterministic, no entropy change
# Primary benefit is unambiguous verbal communication
0.0
end
end
end
# Use NATO phonetic for radio communication
config = ExkPasswd.Config.new!(
num_words: 2,
word_length: 4..5,
meta: %{
transforms: [%MyApp.PhoneticTransform{format: :abbreviated}]
}
)
ExkPasswd.generate(config)
#=> "Cha-Ech-Ech-Kil-Oscar (spoken: Charlie-Echo-Echo-Kilo-Oscar)"
```
See `ExkPasswd.Transform` documentation for more examples including:
- Prefix/suffix transforms
- Case transforms
- Unicode normalization
- Chaining multiple transforms
#### Custom Dictionaries
Use your own word lists:
```elixir
# Load custom dictionary
custom_words = ["apple", "banana", "cherry", "date", "elderberry"]
ExkPasswd.Dictionary.load_custom(:fruits, custom_words)
# Use custom dictionary
config = ExkPasswd.Config.new!(
num_words: 3,
dictionary: :fruits
)
ExkPasswd.generate(config)
#=> "45?apple?CHERRY?date?89"
```
---
## Development
### Quick Reference
```bash
# Setup
mix setup # Install and compile dependencies
# Testing
mix test # Run tests with coverage
mix test.watch # Run tests in watch mode
# Code Quality
mix format # Format code
mix credo --strict # Run linter
mix check # Run format, credo, and tests
mix check.all # Run all checks including dialyzer
# Benchmarks
mix bench # Run all benchmarks
mix bench.password # Benchmark password generation
mix bench.dict # Benchmark dictionary operations
# Documentation
mix docs # Generate documentation
# Security
mix hex.audit # Check for vulnerable dependencies
mix deps.audit # Run mix_audit security scan
```
### Running Tests
```bash
mix test # Run all tests
mix coveralls.html # Run with coverage
mix test.watch # Run in watch mode
```
### Code Quality
```bash
mix format # Format code
mix credo --strict # Run Credo analysis
mix dialyzer # Run Dialyzer
mix check # Run all checks (format, credo, tests)
mix check.all # Run all checks including dialyzer
```
### Building Documentation
```bash
mix docs # Generate documentation
open doc/index.html # ..then open in browser
```
### Running Benchmarks
```bash
mix bench # Run all benchmarks
mix bench.password # Password generation benchmarks
mix bench.dict # Dictionary operations benchmarks
mix bench.batch # Batch generation benchmarks
```
Benchmarks measure:
- Password generation performance across different presets
- Dictionary lookup performance (constant-time tuple indexing)
- Case transformation overhead
- Batch vs individual generation performance
**Benchmark Results:**
- [Password Generation Benchmarks](bench/output/password_generation.md)
- [Dictionary Benchmarks](bench/output/dictionary.md)
- [Batch Generation Benchmarks](bench/output/batch.md)
---
## Contributing
We welcome contributions!
### Development Setup
1. Fork the repository
2. Clone your fork: `git clone https://github.com/futhr/exk_passwd.git`
3. Install dependencies: `mix deps.get`
4. Run tests: `mix test`
5. Make your changes
6. Submit a pull request
---
## Releasing
This project uses [git_ops](https://hex.pm/packages/git_ops) for automated releases.
```bash
mix release # Bumps version, updates CHANGELOG, commits, and tags
git push --follow-tags # Pushes commit and tag
```
CI (`publish.yml`) triggers on `v*` tag → runs checks → `mix hex.publish`
---
## License
BSD-2-Clause License - see [LICENSE](LICENSE.md) file for details.
---
## Resources
- [Documentation](https://github.com/futhr/exk_passwd)
- [GitHub Repository](https://github.com/futhr/exk_passwd)
- [Issue Tracker](https://github.com/futhr/exk_passwd/issues)
- [Changelog](CHANGELOG.md)
- [Original Perl Module](https://github.com/bbusschots/hsxkpasswd)
- [XKCD Comic #936](https://xkcd.com/936/)
---
## Acknowledgments
- Original concept from the [XKCD "Password Strength" comic](https://xkcd.com/936/)
- Based on [Crypt::HSXKPasswd](https://github.com/bbusschots/hsxkpasswd) by Bart Busschots
- Based on [westbaystars/exk_passwd](https://github.com/westbaystars/exk_passwd) by Michael Westbay
- Word list from the [EFF Large Wordlist](https://www.eff.org/deeplinks/2016/07/new-wordlists-random-passphrases) by the Electronic Frontier Foundation
---
**Built with Elixir ❤️ Secure by Design**