Class: Zxcvbn::Matchers::L33t

Inherits:
Object
  • Object
show all
Defined in:
lib/zxcvbn/matchers/l33t.rb,
lib/zxcvbn/matchers/new_l33t.rb

Constant Summary collapse

L33T_TABLE =
{
  'a' => ['4', '@'],
  'b' => ['8'],
  'c' => ['(', '{', '[', '<'],
  'e' => ['3'],
  'g' => ['6', '9'],
  'i' => ['1', '!', '|'],
  'l' => ['1', '|', '7'],
  'o' => ['0'],
  's' => ['$', '5'],
  't' => ['+', '7'],
  'x' => ['%'],
  'z' => ['2']
}

Instance Method Summary collapse

Constructor Details

#initialize(dictionary_matchers) ⇒ L33t

Returns a new instance of L33t.



19
20
21
# File 'lib/zxcvbn/matchers/l33t.rb', line 19

def initialize(dictionary_matchers)
  @dictionary_matchers = dictionary_matchers
end

Instance Method Details

#dedup(subs) ⇒ Object



110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
# File 'lib/zxcvbn/matchers/l33t.rb', line 110

def dedup(subs)
  deduped = []
  members = []
  subs.each do |sub|
    assoc = sub.dup

    assoc.sort! rescue debugger
    label = assoc.map{|k, v| "#{k},#{v}"}.join('-')
    unless members.include?(label)
      members << label
      deduped << sub
    end
  end
  deduped
end

#expanded_substitutions(hash) ⇒ Object

expand possible combinations if multiple characters can be substituted e.g. => [‘4’, ‘@’], ‘i’ => [‘1’] expands to

[{'a' => '4', 'i' => 1}, {'a' => '@', 'i' => '1'}]


111
112
113
114
115
116
# File 'lib/zxcvbn/matchers/new_l33t.rb', line 111

def expanded_substitutions(hash)
  return {} if hash.empty?
  values = hash.values
  product_values = values[0].product(*values[1..-1])
  product_values.map{ |p| Hash[hash.keys.zip(p)] }
end

#find_substitutions(subs, table, keys) ⇒ Object



80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
# File 'lib/zxcvbn/matchers/l33t.rb', line 80

def find_substitutions(subs, table, keys)
  return subs if keys.empty?
  first_key = keys[0]
  rest_keys = keys[1..-1]
  next_subs = []
  table[first_key].each do |l33t_char|
    subs.each do |sub|
      dup_l33t_index = -1
      (0...sub.length).each do |i|
        if sub[i][0] == l33t_char
          dup_l33t_index = i
          break
        end
      end

      if dup_l33t_index == -1
        sub_extension = sub + [[l33t_char, first_key]]
        next_subs << sub_extension
      else
        sub_alternative = sub.dup
        sub_alternative[dup_l33t_index, 1] = [[l33t_char, first_key]]
        next_subs << sub
        next_subs << sub_alternative
      end
    end
  end
  subs = dedup(next_subs)
  find_substitutions(subs, table, rest_keys)
end

#l33t_subs(table) ⇒ Object



65
66
67
68
69
70
71
72
73
74
75
76
77
78
# File 'lib/zxcvbn/matchers/l33t.rb', line 65

def l33t_subs(table)
  keys = table.keys
  subs = [[]]
  subs = find_substitutions(subs, table, keys)
  new_subs = []
  subs.each do |sub|
    hash = {}
    sub.each do |l33t_char, chr|
      hash[l33t_char] = chr
    end
    new_subs << hash
  end
  new_subs
end

#matches(password) ⇒ Object



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
# File 'lib/zxcvbn/matchers/l33t.rb', line 23

def matches(password)
  matches = []
  lowercased_password = password.downcase
  combinations_to_try = l33t_subs(relevent_l33t_subtable(lowercased_password))
  combinations_to_try.each do |substitution|
    @dictionary_matchers.each do |matcher|
      subbed_password = translate(lowercased_password, substitution)
      matcher.matches(subbed_password).each do |match|
        token = password[match.i..match.j]
        next if token.downcase == match.matched_word.downcase
        match_substitutions = {}
        substitution.each do |substitution, letter|
          match_substitutions[substitution] = letter if token.include?(substitution)
        end
        match.l33t = true
        match.token = password[match.i..match.j]
        match.sub = match_substitutions
        match.sub_display = match_substitutions.map do |k, v|
          "#{k} -> #{v}"
        end.join(', ')
        matches << match
      end
    end
  end
  matches
end

#relevant_l33t_substitutions(password) ⇒ Object

produces a l33t table of substitutions present in the given password



61
62
63
64
65
66
67
68
69
70
71
72
73
# File 'lib/zxcvbn/matchers/new_l33t.rb', line 61

def relevant_l33t_substitutions(password)
  subs = Hash.new do |hash, key|
    hash[key] = []
  end
  L33T_TABLE.each do |letter, substibutions|
    password.each_char do |password_char|
      if substibutions.include?(password_char)
        subs[letter] << password_char
      end
    end
  end
  subs
end

#relevent_l33t_subtable(password) ⇒ Object



56
57
58
59
60
61
62
63
# File 'lib/zxcvbn/matchers/l33t.rb', line 56

def relevent_l33t_subtable(password)
  filtered = {}
  L33T_TABLE.each do |letter, subs|
    relevent_subs = subs.select { |s| password.include?(s) }
    filtered[letter] = relevent_subs unless relevent_subs.empty?
  end
  filtered
end

#substitute(password, substitution) ⇒ Object



52
53
54
55
56
57
58
# File 'lib/zxcvbn/matchers/new_l33t.rb', line 52

def substitute(password, substitution)
  subbed_password = password.dup
  substitution.each do |letter, substitution|
    subbed_password.gsub!(substitution, letter)
  end
  subbed_password
end

#substitution_combinations(subs_hash) ⇒ Object

takes a character substitutions hash and produces an array of all possible substitution combinations



77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
# File 'lib/zxcvbn/matchers/new_l33t.rb', line 77

def substitution_combinations(subs_hash)
  combinations = []
  expanded_substitutions = expanded_substitutions(subs_hash)

  # build an array of all possible combinations
  expanded_substitutions.each do |substitution_hash|
    # convert a hash to an array of hashes with 1 key each
    subs_array = substitution_hash.map do |letter, substitutions|
      {letter => substitutions}
    end
    combinations << subs_array

    # find all possible combinations for each number of combinations available
    subs_array.combination(subs_array.size).each do |combination|
      # Don't add duplicates
      combinations << combination unless combinations.include?(combination)
    end
  end

  # convert back to simple hash per substitution combination
  combination_hashes = combinations.map do |combination_set|
    hash = {}
    combination_set.each do |combination_hash|
      hash.merge!(combination_hash)
    end
    hash
  end

  combination_hashes
end

#translate(password, sub) ⇒ Object



50
51
52
53
54
# File 'lib/zxcvbn/matchers/l33t.rb', line 50

def translate(password, sub)
  password.split('').map do |chr|
    sub[chr] || chr
  end.join
end