zxcvbn-ruby 0.0.2 → 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
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