Class: PassForge::Analyzer

Inherits:
Object
  • Object
show all
Defined in:
lib/passforge/analyzer.rb

Overview

Password strength analyzer Evaluates password security and provides recommendations

Defined Under Namespace

Classes: Result

Class Method Summary collapse

Class Method Details

.analyze(password) ⇒ Result

Analyze password strength

Examples:

Analyze a password

result = PassForge::Analyzer.analyze("MyP@ssw0rd")
result.strength  # => :fair
result.entropy   # => 45.6
result.suggestions  # => ["Add more characters", "Include symbols"]

Raises:

  • (ArgumentError)


42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
# File 'lib/passforge/analyzer.rb', line 42

def self.analyze(password)
  raise ArgumentError, "Password cannot be empty" if password.nil? || password.empty?

  entropy = calculate_entropy(password)
  crack_time = estimate_crack_time(entropy)
  strength = determine_strength(entropy, password)
  score = calculate_score(entropy, password)
  suggestions = generate_suggestions(password, entropy)

  Result.new(
    password: password,
    score: score,
    entropy: entropy,
    crack_time: crack_time,
    strength: strength,
    suggestions: suggestions
  )
end

.calculate_entropy(password) ⇒ Object

Calculate password entropy (bits of randomness)



63
64
65
66
67
68
69
# File 'lib/passforge/analyzer.rb', line 63

def self.calculate_entropy(password)
  charset_size = determine_charset_size(password)
  length = password.length
  
  # Entropy = log2(charset_size^length)
  Math.log2(charset_size**length)
end

.calculate_score(entropy, password) ⇒ Object

Calculate numeric score (0-100)



127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
# File 'lib/passforge/analyzer.rb', line 127

def self.calculate_score(entropy, password)
  base_score = [entropy * 1.5, 100].min
  
  # Penalties
  base_score -= 10 if password.length < 8
  base_score -= 15 if common_password?(password)
  base_score -= 5 if password =~ /^[a-z]+$/  # All lowercase
  base_score -= 5 if password =~ /^[A-Z]+$/  # All uppercase
  base_score -= 5 if password =~ /^[0-9]+$/  # All numbers
  
  # Bonuses
  base_score += 5 if password.length > 12
  base_score += 5 if password.length > 16
  base_score += 10 if has_all_char_types?(password)
  
  [[base_score, 0].max, 100].min.to_i
end

.common_password?(password) ⇒ Boolean

Check if password is common



156
157
158
159
160
161
162
163
164
# File 'lib/passforge/analyzer.rb', line 156

def self.common_password?(password)
  common_passwords = %w[
    password 123456 12345678 qwerty abc123 monkey 1234567 letmein
    trustno1 dragon baseball iloveyou master sunshine ashley bailey
    passw0rd shadow 123123 654321 superman qazwsx michael football
  ]
  
  common_passwords.include?(password.downcase)
end

.determine_charset_size(password) ⇒ Object

Determine character set size



73
74
75
76
77
78
79
80
# File 'lib/passforge/analyzer.rb', line 73

def self.determine_charset_size(password)
  size = 0
  size += 26 if password =~ /[a-z]/
  size += 26 if password =~ /[A-Z]/
  size += 10 if password =~ /[0-9]/
  size += 32 if password =~ /[^a-zA-Z0-9]/
  size
end

.determine_strength(entropy, password) ⇒ Object

Determine strength level



107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
# File 'lib/passforge/analyzer.rb', line 107

def self.determine_strength(entropy, password)
  # Check for common patterns
  return :very_weak if common_password?(password)
  
  case entropy
  when 0...28
    :very_weak
  when 28...36
    :weak
  when 36...60
    :fair
  when 60...128
    :strong
  else
    :very_strong
  end
end

.estimate_crack_time(entropy) ⇒ Object

Estimate crack time based on entropy



84
85
86
87
88
89
90
# File 'lib/passforge/analyzer.rb', line 84

def self.estimate_crack_time(entropy)
  guesses_per_second = 1_000_000_000 # 1 billion guesses/sec
  total_guesses = 2**entropy
  seconds = total_guesses / guesses_per_second / 2 # Average case
  
  format_time(seconds)
end

.format_time(seconds) ⇒ Object

Format time in human-readable format



94
95
96
97
98
99
100
101
102
103
# File 'lib/passforge/analyzer.rb', line 94

def self.format_time(seconds)
  return "instant" if seconds < 1
  return "#{seconds.to_i} seconds" if seconds < 60
  return "#{(seconds / 60).to_i} minutes" if seconds < 3600
  return "#{(seconds / 3600).to_i} hours" if seconds < 86_400
  return "#{(seconds / 86_400).to_i} days" if seconds < 31_536_000
  return "#{(seconds / 31_536_000).to_i} years" if seconds < 31_536_000_000
  
  "centuries"
end

.generate_suggestions(password, entropy) ⇒ Object

Generate improvement suggestions



168
169
170
171
172
173
174
175
176
177
178
179
180
# File 'lib/passforge/analyzer.rb', line 168

def self.generate_suggestions(password, entropy)
  suggestions = []
  
  suggestions << "Use at least 12 characters" if password.length < 12
  suggestions << "Add uppercase letters" unless password =~ /[A-Z]/
  suggestions << "Add lowercase letters" unless password =~ /[a-z]/
  suggestions << "Add numbers" unless password =~ /[0-9]/
  suggestions << "Add symbols (!@#$%^&*)" unless password =~ /[^a-zA-Z0-9]/
  suggestions << "Avoid common passwords" if common_password?(password)
  suggestions << "Consider using a passphrase" if entropy < 50
  
  suggestions
end

.has_all_char_types?(password) ⇒ Boolean

Check if password has all character types



147
148
149
150
151
152
# File 'lib/passforge/analyzer.rb', line 147

def self.has_all_char_types?(password)
  password =~ /[a-z]/ &&
    password =~ /[A-Z]/ &&
    password =~ /[0-9]/ &&
    password =~ /[^a-zA-Z0-9]/
end