DiceWare Password Generator: Cryptographically Secure Password Generation
DiceWare Password Generator: Cryptographically Secure Password Generation
In this post, I’ll share insights from my DiceWare Password Generator project, which implements the DiceWare method for generating cryptographically secure passwords using Ruby, demonstrating advanced security practices and random number generation techniques.
Project Overview
The DiceWare Password Generator is a Ruby implementation of the DiceWare method, a technique for generating secure passwords using dice rolls and word lists. This project showcases cryptographic security principles, entropy generation, and integration with authentication systems like OpenLDAP.
Technical Architecture
Project Structure
DiceWarePasswordGenerator/
├── lib/
│ ├── diceware/
│ │ ├── generator.rb
│ │ ├── word_list.rb
│ │ ├── entropy.rb
│ │ └── validator.rb
│ ├── crypto/
│ │ ├── random_generator.rb
│ │ ├── hash_functions.rb
│ │ └── encryption.rb
│ └── integration/
│ ├── ldap_client.rb
│ ├── password_manager.rb
│ └── user_interface.rb
├── data/
│ ├── wordlists/
│ │ ├── diceware.txt
│ │ ├── eff_large.txt
│ │ └── custom.txt
│ └── config/
│ ├── settings.yml
│ └── ldap_config.yml
├── tests/
│ ├── unit/
│ ├── integration/
│ └── fixtures/
├── bin/
│ ├── diceware
│ └── password_manager
├── Gemfile
├── Rakefile
└── README.mdCore Implementation
DiceWare Generator
# lib/diceware/generator.rb
require 'securerandom'
require 'digest'
require_relative 'word_list'
require_relative 'entropy'
require_relative 'validator'
module DiceWare
class Generator
attr_reader :word_list, :entropy_calculator, :validator
def initialize(word_list_path = nil, options = {})
@word_list = WordList.new(word_list_path)
@entropy_calculator = Entropy.new
@validator = Validator.new
@options = default_options.merge(options)
end
def generate_password(word_count = 6, separator = ' ')
validate_inputs(word_count, separator)
words = generate_words(word_count)
password = words.join(separator)
{
password: password,
words: words,
entropy: calculate_entropy(word_count),
strength: assess_strength(password),
generated_at: Time.now
}
end
def generate_passphrase(word_count = 6, separator = ' ', include_numbers = false, include_symbols = false)
validate_inputs(word_count, separator)
words = generate_words(word_count)
# Add numbers if requested
if include_numbers
numbers = generate_numbers(word_count)
words = words.zip(numbers).flatten.compact
end
# Add symbols if requested
if include_symbols
symbols = generate_symbols(word_count)
words = words.zip(symbols).flatten.compact
end
password = words.join(separator)
{
password: password,
words: words,
entropy: calculate_entropy(word_count, include_numbers, include_symbols),
strength: assess_strength(password),
generated_at: Time.now
}
end
def generate_secure_password(length = 16, character_set = :all)
validate_password_length(length)
chars = get_character_set(character_set)
password = generate_random_string(length, chars)
{
password: password,
entropy: calculate_entropy_for_chars(length, chars.length),
strength: assess_strength(password),
generated_at: Time.now
}
end
def batch_generate(count = 10, word_count = 6)
passwords = []
count.times do
passwords << generate_password(word_count)
end
passwords
end
def validate_password_strength(password)
@validator.validate(password)
end
private
def default_options
{
use_crypto_random: true,
word_list_size: 7776, # Standard DiceWare word list size
min_word_count: 4,
max_word_count: 20,
entropy_threshold: 128 # bits
}
end
def validate_inputs(word_count, separator)
raise ArgumentError, "Word count must be between #{@options[:min_word_count]} and #{@options[:max_word_count]}" unless
word_count.between?(@options[:min_word_count], @options[:max_word_count])
raise ArgumentError, "Separator cannot be empty" if separator.nil? || separator.empty?
end
def validate_password_length(length)
raise ArgumentError, "Password length must be at least 8 characters" if length < 8
raise ArgumentError, "Password length cannot exceed 256 characters" if length > 256
end
def generate_words(count)
words = []
count.times do
dice_rolls = generate_dice_rolls
word = @word_list.get_word(dice_rolls)
words << word
end
words
end
def generate_dice_rolls
rolls = []
5.times do
if @options[:use_crypto_random]
rolls << SecureRandom.random_number(6) + 1
else
rolls << rand(1..6)
end
end
rolls.join
end
def generate_numbers(count)
numbers = []
count.times do
if @options[:use_crypto_random]
numbers << SecureRandom.random_number(1000)
else
numbers << rand(1000)
end
end
numbers
end
def generate_symbols(count)
symbols = ['!', '@', '#', '$', '%', '^', '&', '*', '(', ')', '-', '_', '=', '+']
generated_symbols = []
count.times do
if @options[:use_crypto_random]
generated_symbols << symbols[SecureRandom.random_number(symbols.length)]
else
generated_symbols << symbols.sample
end
end
generated_symbols
end
def generate_random_string(length, character_set)
password = ''
length.times do
if @options[:use_crypto_random]
password += character_set[SecureRandom.random_number(character_set.length)]
else
password += character_set.sample
end
end
password
end
def get_character_set(type)
case type
when :lowercase
'abcdefghijklmnopqrstuvwxyz'
when :uppercase
'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
when :numbers
'0123456789'
when :symbols
'!@#$%^&*()_+-=[]{}|;:,.<>?'
when :alphanumeric
'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
when :all
'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*()_+-=[]{}|;:,.<>?'
else
raise ArgumentError, "Unknown character set: #{type}"
end
end
def calculate_entropy(word_count, include_numbers = false, include_symbols = false)
@entropy_calculator.calculate(word_count, include_numbers, include_symbols)
end
def calculate_entropy_for_chars(length, character_set_size)
@entropy_calculator.calculate_for_chars(length, character_set_size)
end
def assess_strength(password)
@validator.assess_strength(password)
end
end
endWord List Management
# lib/diceware/word_list.rb
require 'csv'
require 'yaml'
module DiceWare
class WordList
attr_reader :words, :size
def initialize(file_path = nil)
@words = {}
@file_path = file_path || default_word_list_path
load_word_list
end
def get_word(dice_rolls)
raise ArgumentError, "Invalid dice rolls: #{dice_rolls}" unless valid_dice_rolls?(dice_rolls)
word = @words[dice_rolls]
raise "Word not found for dice rolls: #{dice_rolls}" unless word
word
end
def get_word_by_index(index)
raise ArgumentError, "Index out of range" unless index.between?(0, @size - 1)
dice_rolls = index_to_dice_rolls(index)
get_word(dice_rolls)
end
def search_words(pattern)
@words.select { |rolls, word| word.match?(/#{pattern}/i) }
end
def get_random_word
random_rolls = generate_random_rolls
get_word(random_rolls)
end
def add_custom_word(dice_rolls, word)
raise ArgumentError, "Invalid dice rolls: #{dice_rolls}" unless valid_dice_rolls?(dice_rolls)
raise ArgumentError, "Word cannot be empty" if word.nil? || word.empty?
@words[dice_rolls] = word.strip
end
def remove_word(dice_rolls)
@words.delete(dice_rolls)
end
def save_custom_word_list(file_path)
File.open(file_path, 'w') do |file|
@words.each do |rolls, word|
file.puts "#{rolls}\t#{word}"
end
end
end
def load_custom_word_list(file_path)
raise "File not found: #{file_path}" unless File.exist?(file_path)
File.readlines(file_path).each do |line|
next if line.strip.empty? || line.start_with?('#')
parts = line.strip.split("\t")
next unless parts.length >= 2
dice_rolls = parts[0]
word = parts[1]
add_custom_word(dice_rolls, word)
end
end
def statistics
{
total_words: @words.size,
average_word_length: calculate_average_length,
longest_word: find_longest_word,
shortest_word: find_shortest_word,
unique_characters: calculate_unique_characters
}
end
private
def default_word_list_path
File.join(File.dirname(__FILE__), '..', '..', 'data', 'wordlists', 'diceware.txt')
end
def load_word_list
raise "Word list file not found: #{@file_path}" unless File.exist?(@file_path)
File.readlines(@file_path).each_with_index do |line, index|
next if line.strip.empty? || line.start_with?('#')
parts = line.strip.split("\t")
next unless parts.length >= 2
dice_rolls = parts[0]
word = parts[1]
@words[dice_rolls] = word
end
@size = @words.size
end
def valid_dice_rolls?(rolls)
rolls.is_a?(String) && rolls.length == 5 && rolls.match?(/^[1-6]{5}$/)
end
def index_to_dice_rolls(index)
# Convert index to 5-digit dice rolls
rolls = []
temp_index = index
5.times do
rolls.unshift((temp_index % 6) + 1)
temp_index /= 6
end
rolls.join
end
def generate_random_rolls
rolls = []
5.times { rolls << rand(1..6) }
rolls.join
end
def calculate_average_length
return 0 if @words.empty?
total_length = @words.values.sum(&:length)
total_length.to_f / @words.size
end
def find_longest_word
@words.values.max_by(&:length)
end
def find_shortest_word
@words.values.min_by(&:length)
end
def calculate_unique_characters
all_chars = @words.values.join.downcase.chars.uniq
all_chars.size
end
end
endEntropy Calculation
# lib/diceware/entropy.rb
require 'math'
module DiceWare
class Entropy
def calculate(word_count, include_numbers = false, include_symbols = false)
# Base entropy from DiceWare word list (log2(7776) ≈ 12.9 bits per word)
base_entropy = word_count * Math.log2(7776)
# Additional entropy from numbers (log2(1000) ≈ 9.97 bits per number)
if include_numbers
base_entropy += word_count * Math.log2(1000)
end
# Additional entropy from symbols (log2(14) ≈ 3.81 bits per symbol)
if include_symbols
base_entropy += word_count * Math.log2(14)
end
base_entropy.round(2)
end
def calculate_for_chars(length, character_set_size)
length * Math.log2(character_set_size).round(2)
end
def calculate_for_password(password)
# Calculate entropy based on character frequency
char_frequency = calculate_character_frequency(password)
entropy = 0
char_frequency.each do |char, frequency|
probability = frequency.to_f / password.length
entropy -= probability * Math.log2(probability) if probability > 0
end
entropy.round(2)
end
def estimate_cracking_time(entropy_bits, attempts_per_second = 1_000_000_000)
# Estimate time to crack password with given entropy
total_combinations = 2 ** entropy_bits
seconds_to_crack = total_combinations / (2 * attempts_per_second) # Divide by 2 for average case
format_time(seconds_to_crack)
end
def entropy_to_strength_level(entropy_bits)
case entropy_bits
when 0..30
:very_weak
when 31..50
:weak
when 51..80
:moderate
when 81..120
:strong
when 121..160
:very_strong
else
:extremely_strong
end
end
private
def calculate_character_frequency(password)
frequency = Hash.new(0)
password.each_char { |char| frequency[char] += 1 }
frequency
end
def format_time(seconds)
if seconds < 60
"#{seconds.round(2)} seconds"
elsif seconds < 3600
"#{(seconds / 60).round(2)} minutes"
elsif seconds < 86400
"#{(seconds / 3600).round(2)} hours"
elsif seconds < 31536000
"#{(seconds / 86400).round(2)} days"
else
"#{(seconds / 31536000).round(2)} years"
end
end
end
endPassword Validation
# lib/diceware/validator.rb
require 'securerandom'
module DiceWare
class Validator
def initialize
@common_passwords = load_common_passwords
@patterns = load_patterns
end
def validate(password)
results = {
valid: true,
errors: [],
warnings: [],
suggestions: []
}
# Basic validation
validate_length(password, results)
validate_complexity(password, results)
validate_common_passwords(password, results)
validate_patterns(password, results)
validate_entropy(password, results)
results[:valid] = results[:errors].empty?
results
end
def assess_strength(password)
score = 0
feedback = []
# Length scoring
if password.length >= 12
score += 25
elsif password.length >= 8
score += 15
else
score += 5
feedback << "Consider using a longer password"
end
# Character variety scoring
score += assess_character_variety(password, feedback)
# Entropy scoring
entropy_score = assess_entropy(password, feedback)
score += entropy_score
# Pattern scoring
score += assess_patterns(password, feedback)
# Final assessment
strength_level = determine_strength_level(score)
{
score: [score, 100].min,
level: strength_level,
feedback: feedback,
entropy: calculate_password_entropy(password)
}
end
def check_password_policy(password, policy = default_policy)
violations = []
policy.each do |rule, value|
case rule
when :min_length
violations << "Password must be at least #{value} characters long" if password.length < value
when :max_length
violations << "Password must be no more than #{value} characters long" if password.length > value
when :require_uppercase
violations << "Password must contain uppercase letters" unless password.match?(/[A-Z]/)
when :require_lowercase
violations << "Password must contain lowercase letters" unless password.match?(/[a-z]/)
when :require_numbers
violations << "Password must contain numbers" unless password.match?(/\d/)
when :require_symbols
violations << "Password must contain symbols" unless password.match?(/[^a-zA-Z0-9]/)
when :forbid_common_words
violations << "Password contains common words" if contains_common_words?(password)
when :forbid_user_info
violations << "Password contains user information" if contains_user_info?(password)
end
end
{
compliant: violations.empty?,
violations: violations
}
end
private
def default_policy
{
min_length: 8,
max_length: 128,
require_uppercase: true,
require_lowercase: true,
require_numbers: true,
require_symbols: false,
forbid_common_words: true,
forbid_user_info: true
}
end
def validate_length(password, results)
if password.length < 8
results[:errors] << "Password must be at least 8 characters long"
elsif password.length > 128
results[:errors] << "Password cannot exceed 128 characters"
end
if password.length < 12
results[:warnings] << "Consider using a password with at least 12 characters"
end
end
def validate_complexity(password, results)
has_uppercase = password.match?(/[A-Z]/)
has_lowercase = password.match?(/[a-z]/)
has_numbers = password.match?(/\d/)
has_symbols = password.match?(/[^a-zA-Z0-9]/)
complexity_score = [has_uppercase, has_lowercase, has_numbers, has_symbols].count(true)
if complexity_score < 3
results[:warnings] << "Password should contain at least 3 different character types"
end
unless has_uppercase
results[:suggestions] << "Add uppercase letters"
end
unless has_lowercase
results[:suggestions] << "Add lowercase letters"
end
unless has_numbers
results[:suggestions] << "Add numbers"
end
unless has_symbols
results[:suggestions] << "Add symbols"
end
end
def validate_common_passwords(password, results)
if @common_passwords.include?(password.downcase)
results[:errors] << "Password is too common"
end
end
def validate_patterns(password, results)
@patterns.each do |pattern, message|
if password.match?(pattern)
results[:warnings] << message
end
end
end
def validate_entropy(password, results)
entropy = calculate_password_entropy(password)
if entropy < 30
results[:errors] << "Password has insufficient entropy"
elsif entropy < 50
results[:warnings] << "Password has low entropy"
end
end
def assess_character_variety(password, feedback)
score = 0
if password.match?(/[A-Z]/)
score += 10
else
feedback << "Add uppercase letters"
end
if password.match?(/[a-z]/)
score += 10
else
feedback << "Add lowercase letters"
end
if password.match?(/\d/)
score += 10
else
feedback << "Add numbers"
end
if password.match?(/[^a-zA-Z0-9]/)
score += 15
else
feedback << "Add symbols"
end
score
end
def assess_entropy(password, feedback)
entropy = calculate_password_entropy(password)
case entropy
when 0..30
feedback << "Very low entropy"
0
when 31..50
feedback << "Low entropy"
10
when 51..80
feedback << "Moderate entropy"
20
when 81..120
feedback << "Good entropy"
30
else
feedback << "Excellent entropy"
40
end
end
def assess_patterns(password, feedback)
score = 0
# Check for repeated characters
if password.match?(/(.)\1{2,}/)
score -= 10
feedback << "Avoid repeated characters"
end
# Check for sequential patterns
if password.match?(/(abc|bcd|cde|def|efg|fgh|ghi|hij|ijk|jkl|klm|lmn|mno|nop|opq|pqr|qrs|rst|stu|tuv|uvw|vwx|wxy|xyz)/i)
score -= 5
feedback << "Avoid sequential patterns"
end
# Check for keyboard patterns
if password.match?(/(qwerty|asdfgh|zxcvbn|123456)/i)
score -= 15
feedback << "Avoid keyboard patterns"
end
score
end
def determine_strength_level(score)
case score
when 0..30
:very_weak
when 31..50
:weak
when 51..70
:moderate
when 71..85
:strong
when 86..95
:very_strong
else
:extremely_strong
end
end
def calculate_password_entropy(password)
char_frequency = Hash.new(0)
password.each_char { |char| char_frequency[char] += 1 }
entropy = 0
char_frequency.each do |char, frequency|
probability = frequency.to_f / password.length
entropy -= probability * Math.log2(probability) if probability > 0
end
entropy.round(2)
end
def contains_common_words?(password)
common_words = %w[password passwd admin root user login welcome hello world]
password.downcase.split(/[^a-zA-Z]/).any? { |word| common_words.include?(word) }
end
def contains_user_info?(password)
# This would typically check against user profile information
# For now, we'll check for common patterns
false
end
def load_common_passwords
# Load from file or use built-in list
common_passwords = %w[
password 123456 123456789 qwerty abc123 password123
admin root user guest welcome hello world
]
# Load additional common passwords from file if available
common_file = File.join(File.dirname(__FILE__), '..', '..', 'data', 'common_passwords.txt')
if File.exist?(common_file)
common_passwords += File.readlines(common_file).map(&:strip)
end
common_passwords.map(&:downcase).to_set
end
def load_patterns
{
/^[a-z]+$/i => "Consider mixing character types",
/^[A-Z]+$/i => "Consider mixing character types",
/^\d+$/ => "Consider mixing character types",
/(.)\1{3,}/ => "Avoid repeated characters",
/(abc|bcd|cde|def|efg|fgh|ghi|hij|ijk|jkl|klm|lmn|mno|nop|opq|pqr|qrs|rst|stu|tuv|uvw|vwx|wxy|xyz)/i => "Avoid sequential patterns"
}
end
end
endOpenLDAP Integration
# lib/integration/ldap_client.rb
require 'net/ldap'
require 'digest'
module DiceWare
class LDAPClient
def initialize(config)
@config = config
@ldap = Net::LDAP.new(
host: @config[:host],
port: @config[:port],
auth: {
method: :simple,
username: @config[:bind_dn],
password: @config[:bind_password]
}
)
end
def authenticate_user(username, password)
user_dn = find_user_dn(username)
return false unless user_dn
# Test authentication
test_ldap = Net::LDAP.new(
host: @config[:host],
port: @config[:port],
auth: {
method: :simple,
username: user_dn,
password: password
}
)
test_ldap.bind
end
def change_password(username, new_password, old_password = nil)
user_dn = find_user_dn(username)
return false unless user_dn
# Verify old password if provided
if old_password && !authenticate_user(username, old_password)
return false
end
# Hash the new password
hashed_password = hash_password(new_password)
# Update password
@ldap.replace_attribute(user_dn, :userPassword, hashed_password)
end
def set_password(username, new_password)
user_dn = find_user_dn(username)
return false unless user_dn
hashed_password = hash_password(new_password)
@ldap.replace_attribute(user_dn, :userPassword, hashed_password)
end
def create_user(user_info)
user_dn = "uid=#{user_info[:username]},#{@config[:user_base]}"
attributes = {
objectClass: ['inetOrgPerson', 'organizationalPerson', 'person', 'top'],
uid: user_info[:username],
cn: user_info[:full_name],
sn: user_info[:last_name],
givenName: user_info[:first_name],
mail: user_info[:email],
userPassword: hash_password(user_info[:password])
}
@ldap.add(dn: user_dn, attributes: attributes)
end
def delete_user(username)
user_dn = find_user_dn(username)
return false unless user_dn
@ldap.delete(dn: user_dn)
end
def list_users
users = []
@ldap.search(
base: @config[:user_base],
filter: Net::LDAP::Filter.eq('objectClass', 'inetOrgPerson'),
attributes: ['uid', 'cn', 'mail']
) do |entry|
users << {
username: entry[:uid].first,
full_name: entry[:cn].first,
email: entry[:mail].first
}
end
users
end
def find_user_dn(username)
@ldap.search(
base: @config[:user_base],
filter: Net::LDAP::Filter.eq('uid', username),
attributes: ['dn']
).first&.dn
end
private
def hash_password(password)
# Use SSHA (Salted SHA) for LDAP
salt = SecureRandom.random_bytes(4)
digest = Digest::SHA1.digest(password + salt)
"{SSHA}" + Base64.strict_encode64(digest + salt)
end
end
endCommand Line Interface
#!/usr/bin/env ruby
# bin/diceware
require 'optparse'
require 'json'
require_relative '../lib/diceware/generator'
class DiceWareCLI
def initialize
@options = {}
@generator = DiceWare::Generator.new
end
def run
parse_options
case @options[:command]
when 'generate'
generate_passwords
when 'validate'
validate_password
when 'batch'
batch_generate
when 'strength'
check_strength
else
show_help
end
end
private
def parse_options
OptionParser.new do |opts|
opts.banner = "Usage: diceware [command] [options]"
opts.on('-w', '--words COUNT', Integer, 'Number of words to generate') do |count|
@options[:word_count] = count
end
opts.on('-s', '--separator SEP', 'Separator between words') do |sep|
@options[:separator] = sep
end
opts.on('-n', '--numbers', 'Include numbers') do
@options[:include_numbers] = true
end
opts.on('-y', '--symbols', 'Include symbols') do
@options[:include_symbols] = true
end
opts.on('-c', '--count COUNT', Integer, 'Number of passwords to generate') do |count|
@options[:count] = count
end
opts.on('-p', '--password PASSWORD', 'Password to validate') do |password|
@options[:password] = password
end
opts.on('-o', '--output FORMAT', 'Output format (text, json, csv)') do |format|
@options[:output_format] = format
end
opts.on('-f', '--file FILE', 'Output to file') do |file|
@options[:output_file] = file
end
opts.on('-v', '--verbose', 'Verbose output') do
@options[:verbose] = true
end
opts.on('-h', '--help', 'Show help') do
@options[:command] = 'help'
end
end.parse!
# Set defaults
@options[:word_count] ||= 6
@options[:separator] ||= ' '
@options[:count] ||= 1
@options[:output_format] ||= 'text'
@options[:command] ||= 'generate'
end
def generate_passwords
passwords = []
@options[:count].times do
if @options[:include_numbers] || @options[:include_symbols]
result = @generator.generate_passphrase(
@options[:word_count],
@options[:separator],
@options[:include_numbers],
@options[:include_symbols]
)
else
result = @generator.generate_password(@options[:word_count], @options[:separator])
end
passwords << result
end
output_passwords(passwords)
end
def validate_password
unless @options[:password]
puts "Error: Password required for validation"
exit 1
end
result = @generator.validate_password_strength(@options[:password])
case @options[:output_format]
when 'json'
puts JSON.pretty_generate(result)
else
display_validation_result(result)
end
end
def batch_generate
passwords = @generator.batch_generate(@options[:count], @options[:word_count])
output_passwords(passwords)
end
def check_strength
unless @options[:password]
puts "Error: Password required for strength check"
exit 1
end
result = @generator.validate_password_strength(@options[:password])
case @options[:output_format]
when 'json'
puts JSON.pretty_generate(result)
else
display_strength_result(result)
end
end
def output_passwords(passwords)
case @options[:output_format]
when 'json'
output = JSON.pretty_generate(passwords)
when 'csv'
output = generate_csv(passwords)
else
output = generate_text(passwords)
end
if @options[:output_file]
File.write(@options[:output_file], output)
puts "Passwords saved to #{@options[:output_file]}"
else
puts output
end
end
def generate_text(passwords)
output = []
passwords.each_with_index do |result, index|
output << "Password #{index + 1}:"
output << " Password: #{result[:password]}"
if @options[:verbose]
output << " Words: #{result[:words].join(', ')}"
output << " Entropy: #{result[:entropy]} bits"
output << " Strength: #{result[:strength][:level]}"
output << " Generated: #{result[:generated_at]}"
end
output << ""
end
output.join("\n")
end
def generate_csv(passwords)
require 'csv'
CSV.generate do |csv|
csv << ['Password', 'Entropy', 'Strength', 'Generated']
passwords.each do |result|
csv << [
result[:password],
result[:entropy],
result[:strength][:level],
result[:generated_at]
]
end
end
end
def display_validation_result(result)
puts "Password Validation Result:"
puts "Valid: #{result[:valid]}"
unless result[:errors].empty?
puts "Errors:"
result[:errors].each { |error| puts " - #{error}" }
end
unless result[:warnings].empty?
puts "Warnings:"
result[:warnings].each { |warning| puts " - #{warning}" }
end
unless result[:suggestions].empty?
puts "Suggestions:"
result[:suggestions].each { |suggestion| puts " - #{suggestion}" }
end
end
def display_strength_result(result)
puts "Password Strength Analysis:"
puts "Score: #{result[:score]}/100"
puts "Level: #{result[:level]}"
puts "Entropy: #{result[:entropy]} bits"
unless result[:feedback].empty?
puts "Feedback:"
result[:feedback].each { |feedback| puts " - #{feedback}" }
end
end
def show_help
puts <<~HELP
DiceWare Password Generator
Commands:
generate Generate DiceWare passwords
validate Validate password strength
batch Generate multiple passwords
strength Check password strength
Options:
-w, --words COUNT Number of words (default: 6)
-s, --separator SEP Separator between words (default: ' ')
-n, --numbers Include numbers
-y, --symbols Include symbols
-c, --count COUNT Number of passwords to generate
-p, --password PASS Password to validate/check
-o, --output FORMAT Output format (text, json, csv)
-f, --file FILE Output to file
-v, --verbose Verbose output
-h, --help Show this help
Examples:
diceware generate -w 6
diceware generate -w 8 -n -y -c 5
diceware validate -p "mypassword123"
diceware strength -p "correct horse battery staple"
HELP
end
end
# Run CLI if called directly
if __FILE__ == $0
DiceWareCLI.new.run
endTesting Framework
Unit Tests
# tests/unit/test_generator.rb
require 'test/unit'
require_relative '../../lib/diceware/generator'
class TestGenerator < Test::Unit::TestCase
def setup
@generator = DiceWare::Generator.new
end
def test_generate_password
result = @generator.generate_password(6)
assert_not_nil result[:password]
assert_equal 6, result[:words].length
assert result[:entropy] > 0
assert_not_nil result[:strength]
end
def test_generate_passphrase_with_numbers
result = @generator.generate_passphrase(6, ' ', true, false)
assert_not_nil result[:password]
assert result[:password].match?(/\d/)
assert result[:entropy] > 0
end
def test_generate_passphrase_with_symbols
result = @generator.generate_passphrase(6, ' ', false, true)
assert_not_nil result[:password]
assert result[:password].match?(/[^a-zA-Z0-9\s]/)
assert result[:entropy] > 0
end
def test_batch_generate
passwords = @generator.batch_generate(5, 6)
assert_equal 5, passwords.length
passwords.each do |result|
assert_not_nil result[:password]
assert_equal 6, result[:words].length
end
end
def test_validate_password_strength
result = @generator.validate_password_strength("password123")
assert_not_nil result
assert result.key?(:valid)
assert result.key?(:errors)
assert result.key?(:warnings)
end
def test_invalid_word_count
assert_raise(ArgumentError) do
@generator.generate_password(3) # Too few words
end
assert_raise(ArgumentError) do
@generator.generate_password(25) # Too many words
end
end
endLessons Learned
Cryptography and Security
- Entropy: Understanding and calculating password entropy
- Random Number Generation: Secure random number generation techniques
- Password Policies: Implementing comprehensive password validation
- Security Best Practices: Following cryptographic security principles
Ruby Development
- Object-Oriented Design: Clean class hierarchy and separation of concerns
- Error Handling: Robust error handling and validation
- Testing: Comprehensive unit testing for security-critical code
- CLI Development: User-friendly command-line interface design
System Integration
- LDAP Integration: OpenLDAP authentication and user management
- Configuration Management: Flexible configuration system
- Logging: Comprehensive logging for security auditing
- Documentation: Clear documentation for security tools
Future Enhancements
Advanced Features
- Hardware Security Modules: Integration with HSM for key generation
- Multi-Factor Authentication: Integration with MFA systems
- Password Managers: Integration with password manager applications
- Biometric Integration: Support for biometric authentication
Security Improvements
- Advanced Entropy: More sophisticated entropy calculations
- Threat Modeling: Integration with threat modeling tools
- Compliance: Automated compliance checking for password policies
- Audit Logging: Enhanced audit logging and monitoring
Conclusion
The DiceWare Password Generator demonstrates comprehensive cryptographic security practices and Ruby development skills. Key achievements include:
- Cryptographic Security: Implementation of DiceWare method for secure password generation
- Entropy Calculation: Advanced entropy calculation and password strength assessment
- System Integration: OpenLDAP integration for enterprise authentication
- User Interface: Comprehensive CLI and validation tools
- Testing: Thorough testing of security-critical components
- Documentation: Clear documentation and usage examples
The project is available on GitHub and serves as a comprehensive example of cryptographic security implementation and password management systems.
This project represents my deep dive into cryptographic security and demonstrates how secure password generation can be implemented using established methods like DiceWare. The lessons learned here continue to influence my approach to security-critical applications and authentication systems.