Packages
Secure, memorable password generation using the XKPasswd method. Combines random words with configurable transforms to create strong, easy-to-remember passwords. Zero runtime dependencies, uses only Elixir stdlib and :crypto.
Current section
Files
Jump to
Current section
Files
exk_passwd
usage-rules.md
usage-rules.md
# ExkPasswd Usage Rules for AI Agents
This document provides condensed guidance for AI agents working with ExkPasswd, an Elixir password generation library based on the XKPasswd concept.
## Core Principles
1. **Security First**: Always use cryptographically secure randomness via `:crypto.strong_rand_bytes/1`
2. **Zero Dependencies**: Library uses only Elixir stdlib and `:crypto` module
3. **Explicit Configuration**: Use Config structs, not application config
4. **EFF Wordlist**: Uses the 7,772-word EFF Large Wordlist for high entropy (12.9 bits/word)
## Primary API Functions
### Basic Password Generation
```elixir
# Default settings
ExkPasswd.generate()
#=> "45?clever?FOREST?mountain?89"
# Use preset (atom or string)
ExkPasswd.generate(:xkcd)
#=> "correct-horse-battery-staple-amazing"
# Custom configuration with keyword list
ExkPasswd.generate(num_words: 4, separator: "-", case_transform: :capitalize)
#=> "12-Happy-Forest-Dance-56"
# Custom configuration with Config struct
config = ExkPasswd.Config.new!(
num_words: 4,
separator: "-",
case_transform: :capitalize
)
ExkPasswd.generate(config)
#=> "12-Happy-Forest-Dance-56"
# Extend preset with overrides
ExkPasswd.generate(:xkcd, num_words: 6)
#=> "correct-horse-battery-staple-amazing-forest"
```
### Batch Generation (High Performance)
```elixir
# Generate multiple passwords efficiently
ExkPasswd.generate_batch(100)
ExkPasswd.generate_batch(50, config)
# Ensure uniqueness
ExkPasswd.generate_unique_batch(100)
# Parallel generation (multi-core)
ExkPasswd.generate_parallel(1000)
```
### Entropy and Strength Analysis
```elixir
# Calculate entropy
password = ExkPasswd.generate()
config = ExkPasswd.Config.new!()
ExkPasswd.calculate_entropy(password, config)
#=> %{blind: 125.4, seen: 72.3, status: :good, ...}
# Get strength rating
ExkPasswd.strength_rating(password, config)
#=> :good
# Full strength analysis
ExkPasswd.analyze_strength(password, config)
#=> %{rating: :good, score: 72, entropy_bits: 59.2, ...}
```
## Configuration Patterns
### Config Struct Fields
```elixir
ExkPasswd.Config.new!(
# Word selection
num_words: 3, # Number of words (1-10)
word_length: 4..8, # Word length range (4..10)
# Case transformation
case_transform: :alternate, # :none | :alternate | :capitalize | :invert | :lower | :upper | :random
# Separators
separator: "-", # Separator string (or random charset)
# Digits
digits: {2, 2}, # {before, after} - (0-5 each)
# Padding
padding: %{
char: "!", # Padding character (or random 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
},
# Character substitutions (leetspeak)
substitutions: %{"a" => "@", "e" => "3"},
substitution_mode: :none, # :none | :always | :random
# Dictionary
dictionary: :eff, # :eff or custom atom
# Metadata for extensions
meta: %{}
)
```
### Available Presets
```elixir
# Get preset by name
ExkPasswd.Config.Presets.get(:xkcd)
ExkPasswd.Config.Presets.get(:wifi)
ExkPasswd.Config.Presets.get("web32") # String also works
# Available presets:
# - :default - Balanced security (~59 bits)
# - :xkcd - XKCD-style (~65 bits)
# - :wifi - 63-char WPA2 (~85 bits)
# - :web32 - 32-char limit (~65 bits)
# - :web16 - 16-char limit (~42 bits) ⚠️ LOW SECURITY
# - :apple_id - Apple ID requirements (~55 bits)
# - :security - Security questions (~77 bits)
# List all presets
ExkPasswd.Config.Presets.list()
#=> [:default, :xkcd, :wifi, :web32, :web16, :apple_id, :security]
# Get all preset configs
ExkPasswd.Config.Presets.all()
```
### Register Custom Presets
Runtime registration requires the preset registry in the application's
supervision tree (`{ExkPasswd.Config.Presets, []}`); built-in presets work
without it.
```elixir
# Register a new preset
custom = ExkPasswd.Config.new!(num_words: 8, separator: "_")
ExkPasswd.Config.Presets.register(:super_strong, custom)
# Register by extending existing
ExkPasswd.Config.Presets.register(:strong_wifi, :wifi, num_words: 8, digits: {6, 6})
# Use custom preset
ExkPasswd.generate(:super_strong)
```
## Advanced Features
### Custom Dictionaries
Custom dictionaries are stored in `:persistent_term`: they survive the loading
process and reads are zero-copy, but each load triggers a global GC scan, so
load once at application start rather than in hot paths.
```elixir
# Load custom word list (once, at startup)
ExkPasswd.Dictionary.load_custom(:spanish, ["casa", "perro", "gato", "libro"])
# Use custom dictionary
config = ExkPasswd.Config.new!(dictionary: :spanish, num_words: 3)
ExkPasswd.generate(config)
# Remove when no longer needed
ExkPasswd.Dictionary.delete_custom(:spanish)
```
### Transform Protocol (Extensibility)
```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"},
mode: :random
}
]
}
)
ExkPasswd.generate(config)
#=> "45?cl3v3r?FOREST?m0unt@!n?89"
# Create custom transforms
defmodule MyTransform do
defstruct [:options]
defimpl ExkPasswd.Transform do
def apply(%{options: _}, word, _config) do
# Transform word
String.reverse(word)
end
def entropy_bits(%{options: _}, _config), do: 0.0
end
end
```
### Dictionary API (Internal Use)
```elixir
# Get random word in length range
ExkPasswd.Dictionary.random_word_between(4, 8)
ExkPasswd.Dictionary.random_word_between(4, 8, :capitalize)
ExkPasswd.Dictionary.random_word_between(4, 8, :upper, :eff)
# Dictionary info
ExkPasswd.Dictionary.size() #=> 7772
ExkPasswd.Dictionary.min_length() #=> 3
ExkPasswd.Dictionary.max_length() #=> 9
ExkPasswd.Dictionary.count_between(4, 8, :eff)
```
## Security Rules
### ✅ ALWAYS Use Cryptographically Secure Random
```elixir
# ✅ Correct - Cryptographically secure
:crypto.strong_rand_bytes(4) |> :binary.decode_unsigned()
# ❌ NEVER DO THIS - Predictable and insecure
:rand.uniform(100)
Enum.random(list)
```
### Password Strength Guidelines
- **Minimum 3 words** for basic security
- **4 words** for good security (~52 bits entropy)
- **5 words** for strong security (~65 bits entropy)
- **6+ words** for excellent security (77+ bits entropy)
### Validation
Config structs are automatically validated. Invalid configurations raise `ArgumentError`:
```elixir
# Invalid settings will raise
ExkPasswd.Config.new!(num_words: 0)
#=> ** (ArgumentError) num_words must be between 1 and 10, got: 0
ExkPasswd.Config.new!(word_length: 10..4)
#=> ** (ArgumentError) word_length range invalid: 10..4 (min must be <= max)
# Safe validation without raising
case ExkPasswd.Config.new(num_words: 0) do
{:ok, config} -> config
{:error, msg} -> IO.puts("Invalid: #{msg}")
end
```
## Common Anti-Patterns
### ❌ Don't: Use application config
```elixir
# ❌ Bad - Application-wide config
config :exk_passwd, default_words: 4
```
### ✅ Do: Use Config struct
```elixir
# ✅ Good - Explicit configuration
config = ExkPasswd.Config.new!(num_words: 4)
ExkPasswd.generate(config)
```
### ❌ Don't: Generate passwords in loops
```elixir
# ❌ Bad - Inefficient for many passwords
for _ <- 1..100, do: ExkPasswd.generate()
```
### ✅ Do: Use batch generation
```elixir
# ✅ Good - 30% faster for 100+ passwords
ExkPasswd.generate_batch(100)
ExkPasswd.generate_parallel(1000) # Multi-core for large batches
```
### ❌ Don't: Manual string concatenation
```elixir
# ❌ Bad - Performance and security issues
words |> Enum.reduce("", fn w, acc -> acc <> w <> "-" end)
```
### ✅ Do: Use the library's built-in generation
```elixir
# ✅ Good - Optimized and secure
ExkPasswd.generate(config)
```
## Error Handling
The library uses "let it crash" philosophy for invalid inputs. Catch `ArgumentError` for validation errors:
```elixir
try do
config = ExkPasswd.Config.new!(num_words: 0)
ExkPasswd.generate(config)
rescue
ArgumentError -> "Invalid configuration"
end
```
## Performance Considerations
### Batch Operations
- Use `generate_batch/2` for 10-100 passwords (~30% faster)
- Use `generate_parallel/2` for 100+ passwords (multi-core scaling)
- Use `generate_unique_batch/2` when uniqueness is required
### Dictionary Loading
Dictionary is loaded at compile-time for zero runtime overhead:
- Words pre-indexed by length
- Case variants pre-computed
- O(1) random word selection via tuple indexing
## Testing Patterns
### Basic Tests
```elixir
test "generates valid password" do
password = ExkPasswd.generate()
assert is_binary(password)
assert String.length(password) > 0
end
test "uses preset correctly" do
password = ExkPasswd.generate(:xkcd)
assert password =~ ~r/\w+-\w+-\w+-\w+-\w+/
end
test "uses config correctly" do
config = ExkPasswd.Config.new!(num_words: 2, separator: "_")
password = ExkPasswd.generate(config)
assert password =~ ~r/\w+_\w+/
end
```
### Security Tests
```elixir
test "generates unique passwords" do
passwords = ExkPasswd.generate_batch(1000)
unique_count = passwords |> Enum.uniq() |> length()
assert unique_count > 995 # Allow tiny collision chance
end
test "no weak patterns" do
for _ <- 1..100 do
password = ExkPasswd.generate() |> String.downcase()
refute String.contains?(password, "password")
refute String.contains?(password, "12345")
end
end
```
## Module Architecture
```
ExkPasswd (main API)
├── ExkPasswd.Config (configuration struct)
│ ├── ExkPasswd.Config.Presets (preset registry)
│ └── ExkPasswd.Config.Schema (validation)
├── ExkPasswd.Password (core generation)
├── ExkPasswd.Dictionary (word list management)
├── ExkPasswd.Transform (protocol for custom transforms)
│ ├── ExkPasswd.Transform.CaseTransform
│ └── ExkPasswd.Transform.Substitution
├── ExkPasswd.Random (secure random utilities)
├── ExkPasswd.Entropy (entropy calculation)
├── ExkPasswd.Strength (strength analysis)
├── ExkPasswd.Batch (optimized batch generation)
├── ExkPasswd.Buffer (buffered random for performance)
├── ExkPasswd.Token (number/symbol generation)
└── ExkPasswd.Validator (validation behaviour)
```
## Version Requirements
- Elixir >= 1.16
- Erlang OTP >= 26
- No external runtime dependencies
## Documentation
All public functions are documented with examples. Use `h ExkPasswd.generate` in IEx or visit HexDocs for full reference.