zxcvbn-ruby 0.0.2 → 0.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/lib/zxcvbn.rb CHANGED
@@ -1,3 +1,6 @@
1
+ require 'json'
2
+ require 'pathname'
3
+
1
4
  require 'zxcvbn/version'
2
5
  require 'zxcvbn/match'
3
6
  require 'zxcvbn/matchers/regex_helpers'
@@ -23,8 +26,14 @@ module Zxcvbn
23
26
 
24
27
  DATA_PATH = Pathname(File.expand_path('../../data', __FILE__))
25
28
  ADJACENCY_GRAPHS = JSON.load(DATA_PATH.join('adjacency_graphs.json').read)
26
- FREQUENCY_LISTS = YAML.load(DATA_PATH.join('frequency_lists.yaml').read)
27
- RANKED_DICTIONARIES = DictionaryRanker.rank_dictionaries(FREQUENCY_LISTS)
29
+ FREQUENCY_LISTS_PATH = DATA_PATH.join("frequency_lists")
30
+ RANKED_DICTIONARIES = DictionaryRanker.rank_dictionaries(
31
+ "english" => FREQUENCY_LISTS_PATH.join("english.txt").read.split,
32
+ "female_names" => FREQUENCY_LISTS_PATH.join("female_names.txt").read.split,
33
+ "male_names" => FREQUENCY_LISTS_PATH.join("male_names.txt").read.split,
34
+ "passwords" => FREQUENCY_LISTS_PATH.join("passwords.txt").read.split,
35
+ "surnames" => FREQUENCY_LISTS_PATH.join("surnames.txt").read.split
36
+ )
28
37
 
29
38
  def test(password, user_inputs = [])
30
39
  zxcvbn = PasswordStrength.new
@@ -3,21 +3,16 @@
3
3
  module Zxcvbn
4
4
  class DictionaryRanker
5
5
  def self.rank_dictionaries(lists)
6
- dictionaries = {}
7
- lists.each do |dict_name, words|
6
+ lists.each_with_object({}) do |(dict_name, words), dictionaries|
8
7
  dictionaries[dict_name] = rank_dictionary(words)
9
8
  end
10
- dictionaries
11
9
  end
12
10
 
13
11
  def self.rank_dictionary(words)
14
- dictionary = {}
15
- i = 1
16
- words.each do |word|
17
- dictionary[word.downcase] = i
18
- i += 1
12
+ words.each_with_index
13
+ .with_object({}) do |(word, i), dictionary|
14
+ dictionary[word.downcase] = i + 1
19
15
  end
20
- dictionary
21
16
  end
22
17
  end
23
18
  end
@@ -3,24 +3,25 @@ module Zxcvbn::Entropy
3
3
 
4
4
  def calc_entropy(match)
5
5
  return match.entropy unless match.entropy.nil?
6
- # debugger
6
+
7
7
  match.entropy = case match.pattern
8
- when 'repeat'
9
- repeat_entropy(match)
10
- when 'sequence'
11
- sequence_entropy(match)
12
- when 'digits'
13
- digits_entropy(match)
14
- when 'year'
15
- year_entropy(match)
16
- when 'date'
17
- date_entropy(match)
18
- when 'spatial'
19
- spatial_entropy(match)
20
- when 'dictionary'
21
- dictionary_entropy(match)
22
- end
23
- match.entropy ||= 0
8
+ when 'repeat'
9
+ repeat_entropy(match)
10
+ when 'sequence'
11
+ sequence_entropy(match)
12
+ when 'digits'
13
+ digits_entropy(match)
14
+ when 'year'
15
+ year_entropy(match)
16
+ when 'date'
17
+ date_entropy(match)
18
+ when 'spatial'
19
+ spatial_entropy(match)
20
+ when 'dictionary'
21
+ dictionary_entropy(match)
22
+ else
23
+ 0
24
+ end
24
25
  end
25
26
 
26
27
  def repeat_entropy(match)
@@ -66,7 +67,7 @@ module Zxcvbn::Entropy
66
67
  entropy += 2
67
68
  end
68
69
 
69
- entropy
70
+ entropy
70
71
  end
71
72
 
72
73
  def dictionary_entropy(match)
@@ -90,7 +91,7 @@ module Zxcvbn::Entropy
90
91
  num_upper = word.chars.count{|c| c.match(/[A-Z]/) }
91
92
  num_lower = word.chars.count{|c| c.match(/[a-z]/) }
92
93
  possibilities = 0
93
- (0..min(num_upper, num_lower)).each do |i|
94
+ (0..[num_upper, num_lower].min).each do |i|
94
95
  possibilities += nCk(num_upper + num_lower, i)
95
96
  end
96
97
  lg(possibilities)
@@ -103,7 +104,7 @@ module Zxcvbn::Entropy
103
104
  match.sub.each do |subbed, unsubbed|
104
105
  num_subbed = word.chars.count{|c| c == subbed}
105
106
  num_unsubbed = word.chars.count{|c| c == unsubbed}
106
- (0..min(num_subbed, num_unsubbed)).each do |i|
107
+ (0..[num_subbed, num_unsubbed].min).each do |i|
107
108
  possibilities += nCk(num_subbed + num_unsubbed, i)
108
109
  end
109
110
  end
@@ -131,16 +132,16 @@ module Zxcvbn::Entropy
131
132
  possibilities += nCk(i - 1, j - 1) * starting_positions * average_degree ** j
132
133
  end
133
134
  end
134
-
135
+
135
136
  entropy = lg possibilities
136
137
  # add extra entropy for shifted keys. (% instead of 5, A instead of a.)
137
138
  # math is similar to extra entropy from uppercase letters in dictionary matches.
138
-
139
+
139
140
  if match.shifted_count
140
141
  shiffted_count = match.shifted_count
141
142
  unshifted_count = match.token.length - match.shifted_count
142
143
  possibilities = 0
143
-
144
+
144
145
  (0..[shiffted_count, unshifted_count].min).each do |i|
145
146
  possibilities += nCk(shiffted_count + unshifted_count, i)
146
147
  end
@@ -148,4 +149,4 @@ module Zxcvbn::Entropy
148
149
  end
149
150
  entropy
150
151
  end
151
- end
152
+ end
data/lib/zxcvbn/match.rb CHANGED
@@ -3,11 +3,9 @@ require 'ostruct'
3
3
  module Zxcvbn
4
4
  class Match < OpenStruct
5
5
  def to_hash
6
- hash = @table.dup
7
- hash.keys.sort.each do |key|
8
- hash[key.to_s] = hash.delete(key)
6
+ @table.keys.sort.each_with_object({}) do |key, hash|
7
+ hash[key.to_s] = @table[key]
9
8
  end
10
- hash
11
9
  end
12
10
  end
13
- end
11
+ end
@@ -22,23 +22,23 @@ module Zxcvbn
22
22
  WITHOUT_SEPARATOR = /\d{4,8}/
23
23
 
24
24
  def matches(password)
25
- result = []
26
- result += match_with_separator(password)
27
- result += match_without_separator(password)
28
- result
25
+ match_with_separator(password) + match_without_separator(password)
29
26
  end
30
27
 
31
28
  def match_with_separator(password)
32
29
  result = []
33
30
  re_match_all(YEAR_SUFFIX, password) do |match, re_match|
34
31
  match.pattern = 'date'
35
- match.day = re_match[1].to_i
36
32
  match.separator = re_match[2]
37
- match.month = re_match[3].to_i
38
33
  match.year = re_match[4].to_i
39
34
 
40
- day, month = match.day, match.month
41
- if month > 12
35
+ day = re_match[1].to_i
36
+ month = re_match[3].to_i
37
+
38
+ if month <= 12
39
+ match.day = day
40
+ match.month = month
41
+ else
42
42
  match.day = month
43
43
  match.month = day
44
44
  end
@@ -54,7 +54,6 @@ module Zxcvbn
54
54
  extract_dates(match.token).each do |candidate|
55
55
  day, month, year = candidate[:day], candidate[:month], candidate[:year]
56
56
 
57
- match = match.dup
58
57
  match.pattern = 'date'
59
58
  match.day = day
60
59
  match.month = month
@@ -80,9 +79,9 @@ module Zxcvbn
80
79
  candidate.each do |component, value|
81
80
  candidate[component] = value.to_i
82
81
  end
83
-
82
+
84
83
  candidate[:year] = expand_year(candidate[:year])
85
-
84
+
86
85
  if valid_date?(candidate[:day], candidate[:month], candidate[:year]) && !matches_year?(token)
87
86
  dates << candidate
88
87
  end
@@ -131,4 +130,4 @@ module Zxcvbn
131
130
  end
132
131
  end
133
132
  end
134
- end
133
+ end
@@ -15,4 +15,4 @@ module Zxcvbn
15
15
  end
16
16
  end
17
17
  end
18
- end
18
+ end
@@ -2,20 +2,20 @@ module Zxcvbn
2
2
  module Matchers
3
3
  module RegexHelpers
4
4
  def re_match_all(regex, password)
5
- loop do
6
- re_match = regex.match(password)
7
- break unless re_match
5
+ pos = 0
6
+ while re_match = regex.match(password, pos)
8
7
  i, j = re_match.offset(0)
8
+ pos = j
9
9
  j -= 1
10
+
10
11
  match = Match.new(
11
12
  :i => i,
12
13
  :j => j,
13
14
  :token => password[i..j]
14
15
  )
15
16
  yield match, re_match
16
- password = password.sub(re_match[0], ' ' * re_match[0].length)
17
17
  end
18
18
  end
19
19
  end
20
20
  end
21
- end
21
+ end
@@ -5,28 +5,26 @@ module Zxcvbn
5
5
  result = []
6
6
  i = 0
7
7
  while i < password.length
8
+ cur_char = password[i]
8
9
  j = i + 1
9
- loop do
10
- prev_char, cur_char = password[j-1..j]
11
- if password[j-1] == password[j]
12
- j += 1
13
- else
14
- if j - i > 2 # don't consider length 1 or 2 chains.
15
- result << Match.new(
16
- :pattern => 'repeat',
17
- :i => i,
18
- :j => j-1,
19
- :token => password[i...j],
20
- :repeated_char => password[i]
21
- )
22
- end
23
- break
24
- end
10
+ while cur_char == password[j]
11
+ j += 1
25
12
  end
13
+
14
+ if j - i > 2 # don't consider length 1 or 2 chains.
15
+ result << Match.new(
16
+ :pattern => 'repeat',
17
+ :i => i,
18
+ :j => j-1,
19
+ :token => password[i...j],
20
+ :repeated_char => cur_char
21
+ )
22
+ end
23
+
26
24
  i = j
27
25
  end
28
26
  result
29
27
  end
30
28
  end
31
29
  end
32
- end
30
+ end
@@ -7,58 +7,59 @@ module Zxcvbn
7
7
  'digits' => '01234567890'
8
8
  }
9
9
 
10
+ def seq_match_length(password, from, direction, seq)
11
+ index_from = seq.index(password[from])
12
+ j = 1
13
+ while from + j < password.length &&
14
+ password[from + j] == seq[index_from + direction * j]
15
+ j+= 1
16
+ end
17
+ j
18
+ end
19
+
20
+ # find the first matching sequence, and return with
21
+ # direction, if characters are one apart in the sequence
22
+ def applicable_sequence(password, i)
23
+ SEQUENCES.each do |name, sequence|
24
+ index1 = sequence.index(password[i])
25
+ index2 = sequence.index(password[i+1])
26
+ if index1 and index2
27
+ seq_direction = index2 - index1
28
+ if [-1, 1].include?(seq_direction)
29
+ return [name, sequence, seq_direction]
30
+ else
31
+ return nil
32
+ end
33
+ end
34
+ end
35
+ end
36
+
10
37
  def matches(password)
11
38
  result = []
12
39
  i = 0
13
- while i < password.length-1
14
- j = i + 1
15
- seq = nil # either lower, upper, or digits
16
- seq_name = nil
17
- seq_direction = nil # 1 for ascending seq abcd, -1 for dcba
18
- SEQUENCES.each do |seq_candidate_name, seq_candidate|
19
- seq = nil
40
+ while i < password.length - 1
41
+ seq_name, seq, seq_direction = applicable_sequence(password, i)
20
42
 
21
- i_n, j_n = [password[i], password[j]].map do |chr|
22
- chr ? seq_candidate.index(chr) : nil
23
- end
24
-
25
- if i_n && j_n && i_n > -1 && j_n > -1
26
- direction = j_n - i_n
27
- if [1, -1].include?(direction)
28
- seq = seq_candidate
29
- seq_name = seq_candidate_name
30
- seq_direction = direction
31
- end
32
- end
33
- if seq
34
- loop do
35
- prev_char, cur_char = password[(j-1)], password[j]
36
- prev_n, cur_n = [prev_char, cur_char].map do |chr|
37
- chr ? seq_candidate.index(chr) : nil
38
- end
39
- if prev_n && cur_n && cur_n - prev_n == seq_direction
40
- j += 1
41
- else
42
- if j - i > 2 # don't consider length 1 or 2 chains.
43
- result << Match.new(
44
- :pattern => 'sequence',
45
- :i => i,
46
- :j => j-1,
47
- :token => password[i...j],
48
- :sequence_name => seq_name,
49
- :sequence_space => seq.length,
50
- :ascending => seq_direction == 1
51
- )
52
- end
53
- break
54
- end
55
- end
43
+ if seq
44
+ length = seq_match_length(password, i, seq_direction, seq)
45
+ if length > 2
46
+ result << Match.new(
47
+ :pattern => 'sequence',
48
+ :i => i,
49
+ :j => i + length - 1,
50
+ :token => password[i, length],
51
+ :sequence_name => seq_name,
52
+ :sequence_space => seq.length,
53
+ :ascending => seq_direction == 1
54
+ )
56
55
  end
56
+ i += length - 1
57
+ else
58
+ i += 1
57
59
  end
58
- i = j
59
60
  end
60
61
  result
61
62
  end
62
63
  end
63
64
  end
64
- end
65
+ end
data/lib/zxcvbn/math.rb CHANGED
@@ -40,24 +40,15 @@ module Zxcvbn
40
40
  r
41
41
  end
42
42
 
43
- def min(a, b)
44
- a < b ? a : b
45
- end
46
-
47
43
  def average_degree_for_graph(graph_name)
48
- graph = Zxcvbn::ADJACENCY_GRAPHS[graph_name]
49
- average = 0.0
50
-
51
- graph.each do |key, neighbors|
52
- average += neighbors.compact.length
53
- end
54
-
55
- average /= graph.keys.length
56
- average
44
+ graph = Zxcvbn::ADJACENCY_GRAPHS[graph_name]
45
+ degrees = graph.map { |_, neighbors| neighbors.compact.size }
46
+ sum = degrees.inject(0, :+)
47
+ sum.to_f / graph.size
57
48
  end
58
49
 
59
50
  def starting_positions_for_graph(graph_name)
60
51
  Zxcvbn::ADJACENCY_GRAPHS[graph_name].length
61
52
  end
62
53
  end
63
- end
54
+ end
@@ -1,7 +1,3 @@
1
- require 'json'
2
- require 'yaml'
3
- require 'pathname'
4
-
5
1
  module Zxcvbn
6
2
  class Omnimatch
7
3
  def initialize
@@ -9,11 +5,10 @@ module Zxcvbn
9
5
  end
10
6
 
11
7
  def matches(password, user_inputs = [])
12
- result = []
13
- (@matchers + user_input_matchers(user_inputs)).each do |matcher|
14
- result += matcher.matches(password)
15
- end
16
- result
8
+ matchers = @matchers + user_input_matchers(user_inputs)
9
+ matchers.map do |matcher|
10
+ matcher.matches(password)
11
+ end.inject(&:+)
17
12
  end
18
13
 
19
14
  private
@@ -46,4 +41,4 @@ module Zxcvbn
46
41
  matchers
47
42
  end
48
43
  end
49
- end
44
+ end