ExkPasswd Security Analysis
Mix.install([
{:exk_passwd, "~> 0.1.0"},
{:kino, "~> 0.12"}
])
Introduction
This notebook explores the security aspects of ExkPasswd-generated passwords:
- Entropy - How much randomness/unpredictability
- Strength ratings - Weak, moderate, strong, very strong
- Crack time estimates - How long to brute force
- Comparison - Different configurations and their security
What is Entropy?
Entropy measures the unpredictability of a password in bits. Higher is better.
- Each bit doubles the number of possibilities
- 40 bits = 1 trillion possibilities
- 60 bits = 1 quintillion possibilities
- 128 bits = effectively uncrackable
Calculating Entropy
ExkPasswd provides built-in entropy calculation:
config = ExkPasswd.Config.Presets.get(:default)
password = ExkPasswd.generate(config)
entropy_result = ExkPasswd.Entropy.calculate(password, config)
IO.puts("Password: #{password}")
IO.puts("Blind Entropy: #{Float.round(entropy_result.blind, 2)} bits")
IO.puts("Seen Entropy: #{Float.round(entropy_result.seen, 2)} bits")
IO.puts("Status: #{entropy_result.status}")
IO.puts("Crack time (blind): #{entropy_result.blind_crack_time}")
IO.puts("Crack time (seen): #{entropy_result.seen_crack_time}")
Strength Analysis
Get a comprehensive strength analysis:
config = ExkPasswd.Config.Presets.get(:security)
password = ExkPasswd.generate(config)
strength = ExkPasswd.Strength.analyze(password, config)
IO.puts("Password: #{password}")
IO.puts("Rating: #{strength.rating}")
IO.puts("Score: #{strength.score}/100")
IO.puts("Entropy: #{Float.round(strength.entropy_bits, 2)} bits")
IO.puts("Length: #{String.length(password)} characters")
Comparing Presets
Let’s compare the security of different presets:
presets = [:xkcd, :web32, :wifi, :security, :appleid, :default]
results =
for preset <- presets do
config = ExkPasswd.Config.Presets.get(preset)
password = ExkPasswd.generate(config)
strength = ExkPasswd.Strength.analyze(password, config)
%{
preset: preset,
password: password,
length: String.length(password),
entropy: Float.round(strength.entropy_bits, 1),
rating: strength.rating,
score: strength.score
}
end
# Display as table
IO.puts("\n| Preset | Length | Entropy | Rating | Score |")
IO.puts("|--------|--------|---------|--------|-------|")
for r <- results do
IO.puts(
"| #{r.preset} | #{r.length} | #{r.entropy} bits | #{r.rating} | #{r.score}/100 |"
)
end
:ok
Word Count Impact
How does the number of words affect security?
alias ExkPasswd.Config
word_counts = [2, 3, 4, 5, 6]
IO.puts("\n=== Impact of Word Count on Security ===\n")
for count <- word_counts do
config = Config.new!(num_words: count, case_transform: :lower, separator: "-")
password = ExkPasswd.generate(config)
strength = ExkPasswd.Strength.analyze(password, config)
IO.puts("#{count} words: #{password}")
IO.puts(" Entropy: #{Float.round(strength.entropy_bits, 2)} bits")
IO.puts(" Rating: #{strength.rating}")
IO.puts(" Score: #{strength.score}/100\n")
end
:ok
Word Length Impact
Does using longer words make passwords more secure?
length_configs = [
{3, 4, "very short"},
{4, 6, "short"},
{6, 8, "medium"},
{8, 10, "long"},
{10, 12, "very long"}
]
IO.puts("\n=== Impact of Word Length on Security ===\n")
for {min, max, label} <- length_configs do
config =
Config.new!(
num_words: 3,
word_length: min..max,
separator: "-"
)
password = ExkPasswd.generate(config)
strength = ExkPasswd.Strength.analyze(password, config)
IO.puts("#{label} words (#{min}-#{max} chars): #{password}")
IO.puts(" Entropy: #{Float.round(strength.entropy_bits, 2)} bits")
IO.puts(" Rating: #{strength.rating}\n")
end
:ok
Case Transform Impact
How do case transformations affect security?
transforms = [
:lower,
:upper,
:capitalize,
:alternate,
:random,
:invert
]
IO.puts("\n=== Impact of Case Transform on Security ===\n")
for transform <- transforms do
config = Config.new!(num_words: 3, case_transform: transform, separator: "-")
password = ExkPasswd.generate(config)
strength = ExkPasswd.Strength.analyze(password, config)
IO.puts("#{transform}: #{password}")
IO.puts(" Entropy: #{Float.round(strength.entropy_bits, 2)} bits")
IO.puts(" Rating: #{strength.rating}\n")
end
:ok
Padding Impact
How much does adding numbers and symbols help?
padding_configs = [
{0, 0, 0, 0, "No padding"},
{2, 0, 0, 0, "2 digits before"},
{2, 2, 0, 0, "2 digits before & after"},
{3, 3, 0, 0, "3 digits before & after"},
{2, 2, 1, 1, "Digits + symbols"}
]
IO.puts("\n=== Impact of Padding on Security ===\n")
for {db, da, sb, sa, label} <- padding_configs do
config =
Config.new!(
num_words: 3,
digits: {db, da},
padding: %{char: "!@#$%^&*", before: sb, after: sa, to_length: 0}
)
password = ExkPasswd.generate(config)
strength = ExkPasswd.Strength.analyze(password, config)
IO.puts("#{label}: #{password}")
IO.puts(" Entropy: #{Float.round(strength.entropy_bits, 2)} bits")
IO.puts(" Rating: #{strength.rating}\n")
end
:ok
Cryptographic Randomness
ExkPasswd uses :crypto.strong_rand_bytes/1 for all random operations. This is:
- Cryptographically secure - Unpredictable even with knowledge of previous outputs
- High-quality - Passes statistical randomness tests
- OS-provided - Uses the operating system’s entropy pool
Let’s verify randomness by generating many passwords and checking for duplicates:
# Generate 10,000 passwords and check uniqueness
count = 10_000
passwords = ExkPasswd.Batch.generate_batch(count, :default)
unique_count = Enum.uniq(passwords) |> length()
duplicate_count = count - unique_count
IO.puts("Generated: #{count} passwords")
IO.puts("Unique: #{unique_count}")
IO.puts("Duplicates: #{duplicate_count}")
IO.puts("Uniqueness: #{Float.round(unique_count / count * 100, 2)}%")
if duplicate_count == 0 do
IO.puts("\n✓ Perfect! No collisions detected.")
else
IO.puts("\n⚠ Some duplicates found (expected with high volumes)")
end
Security Recommendations
Based on this analysis:
Minimum Security
For basic security (personal accounts, low-risk):
basic_config = Config.new!(num_words: 3, digits: {0, 2})
basic_pwd = ExkPasswd.generate(basic_config)
basic_strength = ExkPasswd.Strength.analyze(basic_pwd, basic_config)
IO.puts("Basic security config:")
IO.puts("Password: #{basic_pwd}")
IO.puts("Entropy: #{Float.round(basic_strength.entropy_bits, 2)} bits (aim for 50+)")
Strong Security
For important accounts (email, banking, work):
strong_config =
Config.new!(
num_words: 4,
case_transform: :capitalize,
digits: {2, 2}
)
strong_pwd = ExkPasswd.generate(strong_config)
strong_strength = ExkPasswd.Strength.analyze(strong_pwd, strong_config)
IO.puts("Strong security config:")
IO.puts("Password: #{strong_pwd}")
IO.puts("Entropy: #{Float.round(strong_strength.entropy_bits, 2)} bits (aim for 70+)")
Maximum Security
For critical systems (servers, encryption keys, admin accounts):
max_config = ExkPasswd.Config.Presets.get(:security)
max_pwd = ExkPasswd.generate(max_config)
max_strength = ExkPasswd.Strength.analyze(max_pwd, max_config)
IO.puts("Maximum security config:")
IO.puts("Password: #{max_pwd}")
IO.puts("Entropy: #{Float.round(max_strength.entropy_bits, 2)} bits (aim for 100+)")
Interactive Security Explorer
Try different configurations and see their security impact:
# Modify these values and re-run to experiment
my_config =
Config.new(
num_words: 4,
word_length_min: 5,
word_length_max: 8,
case_transform: :capitalize,
separator: "-",
digits: {2, 2},
padding: %{char: "!@#$", before: 0, after: 1, to_length: 0}
)
password = ExkPasswd.generate(my_config)
strength = ExkPasswd.Strength.analyze(password, my_config)
IO.puts("Your password: #{password}")
IO.puts("\n=== Security Analysis ===")
IO.puts("Length: #{String.length(password)} characters")
IO.puts("Entropy: #{Float.round(strength.entropy_bits, 2)} bits")
IO.puts("Rating: #{strength.rating}")
IO.puts("Score: #{strength.score}/100")
Next Steps
- Benchmarks - Performance analysis
- Advanced Usage - Configuration details
- Quick Start - Back to basics
Further Reading
- NIST Digital Identity Guidelines
- OWASP Password Storage Cheat Sheet
- XKCD #936 - The original inspiration