Class: Zxcvbn::Scorer

Inherits:
Object
  • Object
show all
Includes:
CrackTime, Entropy
Defined in:
lib/zxcvbn/scorer.rb

Constant Summary

Constants included from CrackTime

CrackTime::NUM_ATTACKERS, CrackTime::SECONDS_PER_GUESS, CrackTime::SINGLE_GUESS

Constants included from Entropy

Entropy::ALL_LOWER, Entropy::ALL_UPPER, Entropy::END_UPPER, Entropy::NUM_DAYS, Entropy::NUM_MONTHS, Entropy::NUM_YEARS, Entropy::START_UPPER

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from CrackTime

#crack_time_to_score, #display_time, #entropy_to_crack_time

Methods included from Entropy

#calc_entropy, #date_entropy, #dictionary_entropy, #digits_entropy, #extra_l33t_entropy, #extra_uppercase_entropy, #repeat_entropy, #sequence_entropy, #spatial_entropy, #year_entropy

Methods included from Math

#average_degree_for_graph, #bruteforce_cardinality, #lg, #nCk, #starting_positions_for_graph

Constructor Details

#initialize(data) ⇒ Scorer

Returns a new instance of Scorer.



8
9
10
# File 'lib/zxcvbn/scorer.rb', line 8

def initialize(data)
  @data = data
end

Instance Attribute Details

#dataObject (readonly)

Returns the value of attribute data.



12
13
14
# File 'lib/zxcvbn/scorer.rb', line 12

def data
  @data
end

Instance Method Details

#make_bruteforce_match(password, i, j, bruteforce_cardinality) ⇒ Object

fill in the blanks between pattern matches with bruteforce “matches” that way the match sequence fully covers the password: match1.j == match2.i - 1 for every adjacent match1, match2.



91
92
93
94
95
96
97
98
99
100
# File 'lib/zxcvbn/scorer.rb', line 91

def make_bruteforce_match(password, i, j, bruteforce_cardinality)
  Match.new(
    :pattern => 'bruteforce',
    :i => i,
    :j => j,
    :token => password[i..j],
    :entropy => lg(bruteforce_cardinality ** (j - i + 1)),
    :cardinality => bruteforce_cardinality
  )
end

#minimum_entropy_match_sequence(password, matches) ⇒ Object



17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
# File 'lib/zxcvbn/scorer.rb', line 17

def minimum_entropy_match_sequence(password, matches)
  bruteforce_cardinality = bruteforce_cardinality(password) # e.g. 26 for lowercase
  up_to_k = []      # minimum entropy up to k.
  backpointers = [] # for the optimal sequence of matches up to k, holds the final match (match.j == k). null means the sequence ends w/ a brute-force character.
  (0...password.length).each do |k|
    # starting scenario to try and beat: adding a brute-force character to the minimum entropy sequence at k-1.
    previous_k_entropy = k > 0 ? up_to_k[k-1] : 0
    up_to_k[k] = previous_k_entropy + lg(bruteforce_cardinality)
    backpointers[k] = nil
    matches.select do |match|
      match.j == k
    end.each do |match|
      i, j = match.i, match.j
      # see if best entropy up to i-1 + entropy of this match is less than the current minimum at j.
      previous_i_entropy = i > 0 ? up_to_k[i-1] : 0
      candidate_entropy = previous_i_entropy + calc_entropy(match)
      if up_to_k[j] && candidate_entropy < up_to_k[j]
        up_to_k[j] = candidate_entropy
        backpointers[j] = match
      end
    end
  end

  # walk backwards and decode the best sequence
  match_sequence = []
  k = password.length - 1
  while k >= 0
    match = backpointers[k]
    if match
      match_sequence.unshift match
      k = match.i - 1
    else
      k -= 1
    end
  end

  match_sequence = pad_with_bruteforce_matches(match_sequence, password, bruteforce_cardinality)
  score_for(password, match_sequence, up_to_k)
end

#pad_with_bruteforce_matches(match_sequence, password, bruteforce_cardinality) ⇒ Object



73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
# File 'lib/zxcvbn/scorer.rb', line 73

def pad_with_bruteforce_matches(match_sequence, password, bruteforce_cardinality)
  k = 0
  match_sequence_copy = []
  match_sequence.each do |match|
    if match.i > k
      match_sequence_copy << make_bruteforce_match(password, k, match.i - 1, bruteforce_cardinality)
    end
    k = match.j + 1
    match_sequence_copy << match
  end
  if k < password.length
    match_sequence_copy << make_bruteforce_match(password, k, password.length - 1, bruteforce_cardinality)
  end
  match_sequence_copy
end

#score_for(password, match_sequence, up_to_k) ⇒ Object



57
58
59
60
61
62
63
64
65
66
67
68
69
70
# File 'lib/zxcvbn/scorer.rb', line 57

def score_for password, match_sequence, up_to_k
  min_entropy = up_to_k[password.length - 1] || 0  # or 0 corner case is for an empty password ''
  crack_time = entropy_to_crack_time(min_entropy)

  # final result object
  Score.new(
    :password => password,
    :entropy => min_entropy.round(3),
    :match_sequence => match_sequence,
    :crack_time => crack_time.round(3),
    :crack_time_display => display_time(crack_time),
    :score => crack_time_to_score(crack_time)
  )
end