ExkPasswd Advanced Usage
Mix.install([
# {:exk_passwd, "~> 0.1.0"}
{:exk_passwd, git: "https://github.com/futhr/exk_passwd.git"}
])
Introduction
This notebook covers advanced ExkPasswd features including:
- Custom configurations
- Character transformations
- Word length constraints
- Padding and separators
- Character substitutions
Custom Configuration
Instead of using presets, you can create custom configurations:
alias ExkPasswd.Config
# Create a custom configuration
custom_config =
Config.new!(
num_words: 3,
word_length: 5..7,
separator: "_",
padding: %{
before: 1,
after: 3
},
case_transform: :capitalize
)
ExkPasswd.generate(custom_config)
Case Transformations
ExkPasswd supports several case transformation strategies:
# Lowercase: all words lowercase
config_lower = Config.new!(num_words: 3, case_transform: :lower, separator: "-")
IO.puts("Lower: #{ExkPasswd.generate(config_lower)}")
# Uppercase: all words uppercase
config_upper = Config.new!(num_words: 3, case_transform: :upper, separator: "-")
IO.puts("Upper: #{ExkPasswd.generate(config_upper)}")
# Capitalize: first letter of each word capitalized
config_cap = Config.new!(num_words: 3, case_transform: :capitalize, separator: "-")
IO.puts("Capitalize: #{ExkPasswd.generate(config_cap)}")
# Alternate: alternate between UPPER and lower
config_alt = Config.new!(num_words: 4, case_transform: :alternate, separator: "-")
IO.puts("Alternate: #{ExkPasswd.generate(config_alt)}")
# Random: randomly uppercase or lowercase each word
config_rand = Config.new!(num_words: 3, case_transform: :random, separator: "-")
IO.puts("Random: #{ExkPasswd.generate(config_rand)}")
# Invert: fIRST LETTER LOWERCASE, rest uppercase
config_invert = Config.new!(num_words: 3, case_transform: :invert, separator: "-")
IO.puts("Invert: #{ExkPasswd.generate(config_invert)}")
Word Length Control
Control the length of words used in passwords:
# Short words (3-5 characters)
short_config = Config.new!(num_words: 4, word_length: 4..5)
IO.puts("Short: #{ExkPasswd.generate(short_config)}")
# Medium words (6-8 characters)
medium_config = Config.new!(num_words: 3, word_length: 6..8)
IO.puts("Medium: #{ExkPasswd.generate(medium_config)}")
# Long words (9-12 characters)
long_config = Config.new!(num_words: 3, word_length: 9..10)
IO.puts("Long: #{ExkPasswd.generate(long_config)}")
# Exact length
exact_config = Config.new!(num_words: 4, word_length_min: 6, word_length_max: 6)
IO.puts("Exact (6): #{ExkPasswd.generate(exact_config)}")
Separator Characters
Customize how words are joined:
separators = ["-", "_", ".", "!", "@", "#", " ", ""]
for sep <- separators do
config = Config.new!(num_words: 3, separator: sep)
password = ExkPasswd.generate(config)
display_sep = if sep == "", do: "(none)", else: "#{sep}"
IO.puts("Sep #{display_sep}: #{password}")
end
Padding Configuration
Add numbers before and/or after the password:
# Fixed padding: exact number of digits
fixed_config =
Config.new!(
num_words: 3,
digits: {3, 3}
)
IO.puts("Fixed padding: #{ExkPasswd.generate(fixed_config)}")
# Adaptive padding: force password to fixed size
adaptive_config =
Config.new!(
num_words: 3,
padding: %{to_length: 32}
)
IO.puts("Adaptive padding to 32 characters: #{ExkPasswd.generate(adaptive_config)}")
# No padding
none_config = Config.new!(
num_words: 3,
digits: {0, 0},
padding: %{before: 0, after: 0}
)
IO.puts("No padding: #{ExkPasswd.generate(none_config)}")
Symbol Padding
Add symbols before and after:
symbol_config =
Config.new!(
num_words: 3,
padding: %{
char: ~s(!@#$%^&*),
before: 1,
after: 3
}
)
ExkPasswd.generate(symbol_config)
Character Substitutions
Replace letters with numbers/symbols for extra complexity:
# Enable character substitutions (a→@, e→3, i→1, o→0, s→$)
subst_config =
Config.new!(
num_words: 3,
case_transform: :capitalize,
meta: %{
transforms: [
%ExkPasswd.Transform.Substitution{
mode: :always,
map: %{
"a" => "@",
"e" => "3",
"i" => "1",
"o" => "0",
"s" => "$"
}}
]
}
)
ExkPasswd.generate(subst_config)
Building Complex Passwords
Combine multiple features for highly secure passwords:
complex_config =
Config.new!(
num_words: 4,
word_length: 6..9,
separator: "-",
case_transform: :capitalize,
digits: {2, 3},
padding: %{
char: ~s(!@#$%),
before: 1,
after: 1
},
meta: %{
transforms: [
%ExkPasswd.Transform.Substitution{
mode: :always,
map: %{
"a" => "@",
"e" => "3"
}
}
]
}
)
password = ExkPasswd.generate(complex_config)
strength = ExkPasswd.Strength.analyze(password, complex_config)
IO.puts("Password: #{password}")
IO.puts("Length: #{String.length(password)} characters")
IO.puts("Strength: #{strength.rating}")
IO.puts("Entropy: #{Float.round(strength.entropy_bits, 2)} bits")
Batch Generation with Custom Config
Generate multiple passwords with your custom configuration:
my_config = Config.new!(num_words: 4, case_transform: :upper, separator: "_")
passwords = ExkPasswd.Batch.generate_batch(10, my_config)
Enum.with_index(passwords, 1)
|> Enum.each(fn {pwd, idx} ->
IO.puts("#{idx}. #{pwd}")
end)
Configuration Validation
ExkPasswd validates your configuration:
# This will show what happens with invalid config
try do
bad_config = Config.new!(num_words: 0)
ExkPasswd.generate(bad_config)
rescue
e -> IO.inspect(e, label: "Error")
end
# Valid ranges
IO.puts("\nValid configuration ranges:")
IO.puts("num_words: 1-10")
IO.puts("word_length_min: 4-10")
IO.puts("word_length_max: 4-10 (must be >= word_length_min)")
IO.puts("padding_digits: 0-5")
IO.puts("padding_symbols: 0-5")
Saving Custom Configurations
You can save your favorite configurations for reuse:
# Create a module to store your configs
defmodule MyConfigs do
alias ExkPasswd.Config
def gaming do
Config.new!(
num_words: 3,
case_transform: :alternate,
separator: "",
digits: {0, 4}
)
end
def banking do
Config.new!(
num_words: 5,
word_length: 6..10,
case_transform: :capitalize,
digits: {2, 3},
padding: %{
after: 1
}
)
end
end
IO.puts("Gaming: #{ExkPasswd.generate(MyConfigs.gaming())}")
IO.puts("Banking: #{ExkPasswd.generate(MyConfigs.banking())}")
Internationalization (i18n)
ExkPasswd supports Unicode natively and can generate passwords in any language. Custom dictionaries enable domain-specific or language-specific password generation.
Japanese Passwords
# Load a Japanese word dictionary
japanese_words = [
"さくら", # sakura (cherry blossom)
"たいよう", # taiyou (sun)
"うみ", # umi (ocean)
"やま", # yama (mountain)
"かぜ", # kaze (wind)
"ほし", # hoshi (star)
"つき", # tsuki (moon)
"はな", # hana (flower)
"みず", # mizu (water)
"そら" # sora (sky)
]
ExkPasswd.Dictionary.load_custom(:japanese, japanese_words)
# Generate Japanese passwords
japanese_config = ExkPasswd.Config.new!(
num_words: 3,
separator: "-", # Japanese middle dot
word_length_bounds: 2..4,
word_length: 2..4,
digits: {2, 2},
padding: %{char: "*", before: 1, after: 1, to_length: 0},
dictionary: :japanese
)
password_jp = ExkPasswd.generate(japanese_config)
IO.puts("Japanese password: #{password_jp}")
Multi-Language Passwords
# Spanish words
spanish_words = ["casa", "gato", "perro", "sol", "luna", "mar", "cielo", "flor"]
ExkPasswd.Dictionary.load_custom(:spanish, spanish_words)
# German words
german_words = ["haus", "katze", "hund", "sonne", "mond", "meer", "himmel", "blume"]
ExkPasswd.Dictionary.load_custom(:german, german_words)
# French words
french_words = ["maison", "chat", "chien", "soleil", "lune", "mer", "ciel", "fleur"]
ExkPasswd.Dictionary.load_custom(:french, french_words)
# Generate passwords in different languages
languages = [:spanish, :german, :french]
for lang <- languages do
config = ExkPasswd.Config.new!(
num_words: 3,
separator: "-",
digits: {2, 2},
dictionary: lang
)
password = ExkPasswd.generate(config)
IO.puts("#{lang |> to_string() |> String.capitalize()}: #{password}")
end
Unicode Security Considerations
Character Encoding:
- ExkPasswd uses UTF-8 natively (Elixir’s String module)
- String.length/1 returns grapheme count (not byte count)
- Entropy calculations account for character set size
Practical Implications:
# Demonstration of Unicode handling
unicode_words = ["café", "naïve", "résumé", "Zürich", "日本", "🌸🌙"]
ExkPasswd.Dictionary.load_custom(:unicode_demo, unicode_words)
config_unicode = ExkPasswd.Config.new!(
num_words: 3,
word_length_bounds: 2..6,
word_length: 2..6,
separator: ".",
digits: {2, 2},
dictionary: :unicode_demo
)
unicode_pwd = ExkPasswd.generate(config_unicode)
IO.puts("Unicode password: #{unicode_pwd}")
IO.puts("Grapheme length: #{String.length(unicode_pwd)}")
IO.puts("Byte size: #{byte_size(unicode_pwd)}")
# Check entropy
entropy = ExkPasswd.calculate_entropy(unicode_pwd, config_unicode)
IO.puts("Entropy: #{Float.round(entropy.blind, 2)} bits")
Security Notes for i18n Passwords:
- Larger character sets: Non-ASCII adds ~10 bits entropy per character over ASCII
- Input methods: Consider keyboard availability for target users
- System compatibility: Ensure target systems support UTF-8 passwords
- Normalization: Unicode has multiple representations (NFC vs NFD) - store normalized forms
Domain-Specific Dictionaries
Create specialized dictionaries for specific contexts:
# Technical/DevOps terms
tech_words = [
"docker", "kubernetes", "lambda", "cache", "queue",
"cluster", "deploy", "rollback", "metric", "trace"
]
ExkPasswd.Dictionary.load_custom(:devops, tech_words)
# Medical terms (simplified)
medical_words = [
"cardiac", "neural", "hepatic", "renal", "pulmonary",
"vascular", "skeletal", "muscular", "endocrine", "lymphatic"
]
ExkPasswd.Dictionary.load_custom(:medical, medical_words)
# Generate domain-specific passwords
devops_config = ExkPasswd.Config.new!(
num_words: 3,
separator: "_",
case_transform: :lower,
digits: {3, 0},
dictionary: :devops
)
medical_config = ExkPasswd.Config.new!(
num_words: 3,
separator: "-",
case_transform: :capitalize,
digits: {2, 2},
dictionary: :medical
)
IO.puts("DevOps password: #{ExkPasswd.generate(devops_config)}")
IO.puts("Medical password: #{ExkPasswd.generate(medical_config)}")
Mixed-Language Passwords
For multilingual environments, combine dictionaries:
# Combine English + Japanese for bilingual users
bilingual_words = [
"cherry", "sakura", "mountain", "yama",
"ocean", "umi", "forest", "mori",
"river", "kawa", "sky", "sora"
]
ExkPasswd.Dictionary.load_custom(:bilingual, bilingual_words)
bilingual_config = ExkPasswd.Config.new!(
num_words: 4,
separator: ".",
case_transform: :capitalize,
digits: {2, 2},
dictionary: :bilingual
)
bilingual_pwd = ExkPasswd.generate(bilingual_config)
IO.puts("Bilingual password: #{bilingual_pwd}")
Performance with Custom Dictionaries
Custom dictionaries use ETS for O(1) lookups:
# Load large dictionary and benchmark
large_dict = for i <- 1..10_000, do: "word#{i}"
ExkPasswd.Dictionary.load_custom(:large, large_dict)
{time_us, passwords} = :timer.tc(fn ->
ExkPasswd.generate_batch(100, ExkPasswd.Config.new!(dictionary: :large))
end)
IO.puts("Total count: #{Enum.count(passwords)}")
IO.puts("100 passwords from 10k-word dictionary: #{time_us / 1000}ms")
IO.puts("Performance: O(1) ETS lookups maintain constant time regardless of dictionary size")
Next Steps
- Security Analysis - Analyze password strength and entropy
- Benchmarks - Performance testing
- Quick Start - Back to basics