Module: Zxcvbn::Guesses Private
Overview
This module is part of a private API. You should avoid using this module if possible, as it may be removed or be changed in the future.
Mixin that provides guesses estimation for each match pattern.
Each pattern-specific method returns a raw guess count; #estimate_guesses applies a per-token minimum and memoises the result on the match object. Mirrors the guesses estimation logic from zxcvbn.js v4.
Constant Summary collapse
- MIN_GUESSES_BEFORE_GROWING_SEQUENCE =
This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.
10_000- MIN_SUBMATCH_GUESSES_SINGLE_CHAR =
This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.
10- MIN_SUBMATCH_GUESSES_MULTI_CHAR =
This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.
50- BRUTEFORCE_CARDINALITY =
This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.
10- MIN_YEAR_SPACE =
This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.
20- START_UPPER =
This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.
/^[A-Z][^A-Z]+$/- END_UPPER =
This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.
/^[^A-Z]+[A-Z]$/- ALL_UPPER =
This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.
/^[^a-z]+$/- ALL_LOWER =
This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.
/^[^A-Z]+$/
Instance Attribute Summary collapse
- #reference_year ⇒ Object readonly private
Instance Method Summary collapse
-
#bruteforce_guesses(match) ⇒ Numeric
private
Guesses based on token length and assumed cardinality.
-
#date_guesses(match) ⇒ Integer
private
365 * year_space, multiplied by 4 if a separator is present.
-
#dictionary_guesses(match) ⇒ Integer
private
Rank multiplied by uppercase and l33t variation counts, plus a factor of 2 if the word was matched in reverse.
-
#digits_guesses(match) ⇒ Integer
private
10^length (all possible digit strings of that length).
-
#estimate_guesses(match, password) ⇒ Numeric
private
Estimate the number of guesses required to crack a match.
-
#l33t_variations(match) ⇒ Integer
private
Count the number of ways the token’s l33t substitutions could have been chosen.
-
#sequence_guesses(match) ⇒ Integer
private
Guesses based on sequence type and direction.
-
#spatial_guesses(match) ⇒ Numeric
private
Guesses based on graph topology, turns, and shifted keys.
-
#uppercase_variations(match) ⇒ Integer
private
Count the number of ways the token’s capitalisation could have been chosen.
-
#year_guesses(match) ⇒ Integer
private
Distance from the current year, floored at MIN_YEAR_SPACE.
Methods included from Math
#average_degree_for_graph, #nCk, #starting_positions_for_graph
Instance Attribute Details
#reference_year ⇒ Object (readonly)
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
206 207 208 |
# File 'lib/zxcvbn/guesses.rb', line 206 def reference_year @reference_year end |
Instance Method Details
#bruteforce_guesses(match) ⇒ Numeric
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Returns guesses based on token length and assumed cardinality.
66 67 68 69 70 71 72 |
# File 'lib/zxcvbn/guesses.rb', line 66 def bruteforce_guesses(match) length = match.token ? match.token.length : match.j - match.i + 1 guesses = BRUTEFORCE_CARDINALITY**length.to_f guesses = Float::MAX if guesses.infinite? min = length == 1 ? MIN_SUBMATCH_GUESSES_SINGLE_CHAR + 1.0 : MIN_SUBMATCH_GUESSES_MULTI_CHAR + 1.0 [guesses, min].max end |
#date_guesses(match) ⇒ Integer
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Returns 365 * year_space, multiplied by 4 if a separator is present.
104 105 106 107 108 109 |
# File 'lib/zxcvbn/guesses.rb', line 104 def date_guesses(match) year_space = [(match.year - reference_year).abs, MIN_YEAR_SPACE].max guesses = 365 * year_space guesses *= 4 if match.separator && !match.separator.empty? guesses end |
#dictionary_guesses(match) ⇒ Integer
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Returns rank multiplied by uppercase and l33t variation counts, plus a factor of 2 if the word was matched in reverse.
150 151 152 153 154 155 156 |
# File 'lib/zxcvbn/guesses.rb', line 150 def dictionary_guesses(match) match.base_guesses = match.rank match.uppercase_variations = uppercase_variations(match) match.l33t_variations = l33t_variations(match) reversed_multiplier = match.reversed ? 2 : 1 match.base_guesses * match.uppercase_variations * match.l33t_variations * reversed_multiplier end |
#digits_guesses(match) ⇒ Integer
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Returns 10^length (all possible digit strings of that length).
92 93 94 |
# File 'lib/zxcvbn/guesses.rb', line 92 def digits_guesses(match) 10**match.token.length end |
#estimate_guesses(match, password) ⇒ Numeric
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Estimate the number of guesses required to crack a match.
Mutates the builder in place: sets guesses, guesses_log10, and any pattern-specific fields (base_guesses, uppercase_variations, l33t_variations). Returns immediately if guesses are already set.
35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 |
# File 'lib/zxcvbn/guesses.rb', line 35 def estimate_guesses(match, password) return match.guesses if match.guesses token_length = match.token ? match.token.length : match.j - match.i + 1 min_guesses = if token_length < password.length token_length == 1 ? MIN_SUBMATCH_GUESSES_SINGLE_CHAR : MIN_SUBMATCH_GUESSES_MULTI_CHAR else 1 end guesses = case match.pattern in 'bruteforce' then bruteforce_guesses(match) in 'dictionary' then dictionary_guesses(match) in 'spatial' then spatial_guesses(match) in 'repeat' then repeat_guesses(match) in 'sequence' then sequence_guesses(match) in 'digits' then digits_guesses(match) in 'year' then year_guesses(match) in 'date' then date_guesses(match) else 1 end match.guesses = [guesses, min_guesses].max match.guesses_log10 = ::Math.log10(match.guesses) match.guesses end |
#l33t_variations(match) ⇒ Integer
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Count the number of ways the token’s l33t substitutions could have been chosen.
Returns 1 if the match has no l33t substitutions. Otherwise multiplies the variation count for each substituted character pair using combinations.
186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 |
# File 'lib/zxcvbn/guesses.rb', line 186 def l33t_variations(match) return 1 unless match.l33t && match.sub variations = 1 match.sub.each do |subbed, unsubbed| chars = match.token.downcase.chars num_subbed = chars.count { |c| c == subbed } num_unsubbed = chars.count { |c| c == unsubbed } if num_subbed.zero? || num_unsubbed.zero? variations *= 2 else p = [num_subbed, num_unsubbed].min sub_variations = 0 (1..p).each { |i| sub_variations += nCk(num_subbed + num_unsubbed, i) } variations *= sub_variations end end variations end |
#sequence_guesses(match) ⇒ Integer
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Returns guesses based on sequence type and direction.
76 77 78 79 80 81 82 83 84 85 86 87 88 |
# File 'lib/zxcvbn/guesses.rb', line 76 def sequence_guesses(match) first_char = match.token[0] base_guesses = if %w[a A z Z 0 1 9].include?(first_char) 4 elsif first_char.match?(/\d/) 10 else 26 end base_guesses *= 2 unless match.ascending base_guesses * match.token.length end |
#spatial_guesses(match) ⇒ Numeric
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Returns guesses based on graph topology, turns, and shifted keys.
113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 |
# File 'lib/zxcvbn/guesses.rb', line 113 def spatial_guesses(match) if %w[qwerty dvorak].include?(match.graph) s = starting_positions_for_graph('qwerty') d = average_degree_for_graph('qwerty') else s = starting_positions_for_graph('keypad') d = average_degree_for_graph('keypad') end guesses = 0 token_length = match.token.length turns = match.turns (2..token_length).each do |i| possible_turns = [turns, i - 1].min (1..possible_turns).each do |j| guesses += nCk(i - 1, j - 1) * s * (d**j) end end if match.shifted_count&.positive? shifted = match.shifted_count unshifted = token_length - match.shifted_count if unshifted.zero? guesses *= 2 else shift_variations = 0 (1..[shifted, unshifted].min).each { |i| shift_variations += nCk(shifted + unshifted, i) } guesses *= shift_variations end end guesses end |
#uppercase_variations(match) ⇒ Integer
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Count the number of ways the token’s capitalisation could have been chosen.
Returns 1 for all-lowercase or already-lowercase words. Returns 2 for simple patterns (StartUpper, endUPPER, ALLCAPS). Otherwise returns the sum of combinations for mixed-case tokens.
166 167 168 169 170 171 172 173 174 175 176 177 |
# File 'lib/zxcvbn/guesses.rb', line 166 def uppercase_variations(match) word = match.token return 1 if word.match?(ALL_LOWER) || word.downcase == word [START_UPPER, END_UPPER, ALL_UPPER].each { |r| return 2 if word.match?(r) } num_upper = word.chars.count { |c| c.match?(/[A-Z]/) } num_lower = word.chars.count { |c| c.match?(/[a-z]/) } variations = 0 (1..[num_upper, num_lower].min).each { |i| variations += nCk(num_upper + num_lower, i) } variations end |
#year_guesses(match) ⇒ Integer
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Returns distance from the current year, floored at MIN_YEAR_SPACE.
98 99 100 |
# File 'lib/zxcvbn/guesses.rb', line 98 def year_guesses(match) [(match.token.to_i - reference_year).abs, MIN_YEAR_SPACE].max end |