zxcvbn-ruby 1.3.0 → 2.0.0

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.
Files changed (68) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +71 -1
  3. data/README.md +322 -75
  4. data/data/frequency_lists/english_wikipedia.txt +30000 -0
  5. data/data/frequency_lists/female_names.txt +11 -114
  6. data/data/frequency_lists/male_names.txt +3 -24
  7. data/data/frequency_lists/passwords.txt +29623 -6764
  8. data/data/frequency_lists/surnames.txt +28 -30611
  9. data/data/frequency_lists/{english.txt → us_tv_and_film.txt} +147 -13532
  10. data/lib/zxcvbn/clock.rb +6 -0
  11. data/lib/zxcvbn/crack_time.rb +52 -18
  12. data/lib/zxcvbn/data.rb +61 -21
  13. data/lib/zxcvbn/dictionary_ranker.rb +10 -0
  14. data/lib/zxcvbn/feedback.rb +11 -6
  15. data/lib/zxcvbn/feedback_giver.rb +75 -50
  16. data/lib/zxcvbn/guesses.rb +208 -0
  17. data/lib/zxcvbn/match.rb +95 -15
  18. data/lib/zxcvbn/match_builder.rb +15 -0
  19. data/lib/zxcvbn/matchers/date.rb +171 -106
  20. data/lib/zxcvbn/matchers/dictionary.rb +15 -8
  21. data/lib/zxcvbn/matchers/digits.rb +6 -1
  22. data/lib/zxcvbn/matchers/l33t.rb +30 -34
  23. data/lib/zxcvbn/matchers/regex_helpers.rb +14 -6
  24. data/lib/zxcvbn/matchers/repeat.rb +47 -16
  25. data/lib/zxcvbn/matchers/sequences.rb +58 -48
  26. data/lib/zxcvbn/matchers/spatial.rb +22 -6
  27. data/lib/zxcvbn/matchers/year.rb +6 -1
  28. data/lib/zxcvbn/math.rb +15 -28
  29. data/lib/zxcvbn/omnimatch.rb +70 -22
  30. data/lib/zxcvbn/ruby.rb +3 -0
  31. data/lib/zxcvbn/score.rb +34 -10
  32. data/lib/zxcvbn/scorer.rb +142 -75
  33. data/lib/zxcvbn/tester.rb +58 -23
  34. data/lib/zxcvbn/tester_builder.rb +83 -0
  35. data/lib/zxcvbn/trie.rb +21 -0
  36. data/lib/zxcvbn/version.rb +1 -1
  37. data/lib/zxcvbn.rb +47 -7
  38. data/sig/README.md +65 -0
  39. data/sig/zxcvbn/clock.rbs +5 -0
  40. data/sig/zxcvbn/crack_time.rbs +11 -0
  41. data/sig/zxcvbn/data.rbs +40 -0
  42. data/sig/zxcvbn/dictionary_ranker.rbs +7 -0
  43. data/sig/zxcvbn/feedback.rbs +10 -0
  44. data/sig/zxcvbn/feedback_giver.rbs +13 -0
  45. data/sig/zxcvbn/guesses.rbs +36 -0
  46. data/sig/zxcvbn/match.rbs +40 -0
  47. data/sig/zxcvbn/match_builder.rbs +36 -0
  48. data/sig/zxcvbn/matchers/date.rbs +23 -0
  49. data/sig/zxcvbn/matchers/dictionary.rbs +21 -0
  50. data/sig/zxcvbn/matchers/digits.rbs +11 -0
  51. data/sig/zxcvbn/matchers/l33t.rbs +27 -0
  52. data/sig/zxcvbn/matchers/regex_helpers.rbs +7 -0
  53. data/sig/zxcvbn/matchers/repeat.rbs +11 -0
  54. data/sig/zxcvbn/matchers/sequences.rbs +16 -0
  55. data/sig/zxcvbn/matchers/spatial.rbs +15 -0
  56. data/sig/zxcvbn/matchers/year.rbs +11 -0
  57. data/sig/zxcvbn/math.rbs +9 -0
  58. data/sig/zxcvbn/omnimatch.rbs +19 -0
  59. data/sig/zxcvbn/score.rbs +26 -0
  60. data/sig/zxcvbn/scorer.rbs +19 -0
  61. data/sig/zxcvbn/tester.rbs +15 -0
  62. data/sig/zxcvbn/tester_builder.rbs +16 -0
  63. data/sig/zxcvbn/trie.rbs +17 -0
  64. data/sig/zxcvbn.rbs +12 -0
  65. metadata +46 -12
  66. data/lib/zxcvbn/entropy.rb +0 -158
  67. data/lib/zxcvbn/matchers/new_l33t.rb +0 -118
  68. data/lib/zxcvbn/password_strength.rb +0 -27
metadata CHANGED
@@ -1,19 +1,22 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: zxcvbn-ruby
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.3.0
4
+ version: 2.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Steve Hodgkiss
8
8
  - Matthieu Aussaguel
9
+ - Orien Madgwick
9
10
  bindir: bin
10
11
  cert_chain: []
11
12
  date: 1980-01-02 00:00:00.000000000 Z
12
13
  dependencies: []
13
- description: Ruby port of Dropboxs zxcvbn.js
14
+ description: Ruby port of Dropbox's zxcvbn.js JavaScript password strength estimator,
15
+ providing v4 compatibility.
14
16
  email:
15
17
  - steve@hodgkiss.me
16
18
  - matthieu.aussaguel@gmail.com
19
+ - _@orien.io
17
20
  executables: []
18
21
  extensions: []
19
22
  extra_rdoc_files: []
@@ -22,25 +25,26 @@ files:
22
25
  - LICENSE.txt
23
26
  - README.md
24
27
  - data/adjacency_graphs.json
25
- - data/frequency_lists/english.txt
28
+ - data/frequency_lists/english_wikipedia.txt
26
29
  - data/frequency_lists/female_names.txt
27
30
  - data/frequency_lists/male_names.txt
28
31
  - data/frequency_lists/passwords.txt
29
32
  - data/frequency_lists/surnames.txt
33
+ - data/frequency_lists/us_tv_and_film.txt
30
34
  - lib/zxcvbn.rb
31
35
  - lib/zxcvbn/clock.rb
32
36
  - lib/zxcvbn/crack_time.rb
33
37
  - lib/zxcvbn/data.rb
34
38
  - lib/zxcvbn/dictionary_ranker.rb
35
- - lib/zxcvbn/entropy.rb
36
39
  - lib/zxcvbn/feedback.rb
37
40
  - lib/zxcvbn/feedback_giver.rb
41
+ - lib/zxcvbn/guesses.rb
38
42
  - lib/zxcvbn/match.rb
43
+ - lib/zxcvbn/match_builder.rb
39
44
  - lib/zxcvbn/matchers/date.rb
40
45
  - lib/zxcvbn/matchers/dictionary.rb
41
46
  - lib/zxcvbn/matchers/digits.rb
42
47
  - lib/zxcvbn/matchers/l33t.rb
43
- - lib/zxcvbn/matchers/new_l33t.rb
44
48
  - lib/zxcvbn/matchers/regex_helpers.rb
45
49
  - lib/zxcvbn/matchers/repeat.rb
46
50
  - lib/zxcvbn/matchers/sequences.rb
@@ -48,21 +52,51 @@ files:
48
52
  - lib/zxcvbn/matchers/year.rb
49
53
  - lib/zxcvbn/math.rb
50
54
  - lib/zxcvbn/omnimatch.rb
51
- - lib/zxcvbn/password_strength.rb
55
+ - lib/zxcvbn/ruby.rb
52
56
  - lib/zxcvbn/score.rb
53
57
  - lib/zxcvbn/scorer.rb
54
58
  - lib/zxcvbn/tester.rb
59
+ - lib/zxcvbn/tester_builder.rb
55
60
  - lib/zxcvbn/trie.rb
56
61
  - lib/zxcvbn/version.rb
57
- homepage: http://github.com/envato/zxcvbn-ruby
62
+ - sig/README.md
63
+ - sig/zxcvbn.rbs
64
+ - sig/zxcvbn/clock.rbs
65
+ - sig/zxcvbn/crack_time.rbs
66
+ - sig/zxcvbn/data.rbs
67
+ - sig/zxcvbn/dictionary_ranker.rbs
68
+ - sig/zxcvbn/feedback.rbs
69
+ - sig/zxcvbn/feedback_giver.rbs
70
+ - sig/zxcvbn/guesses.rbs
71
+ - sig/zxcvbn/match.rbs
72
+ - sig/zxcvbn/match_builder.rbs
73
+ - sig/zxcvbn/matchers/date.rbs
74
+ - sig/zxcvbn/matchers/dictionary.rbs
75
+ - sig/zxcvbn/matchers/digits.rbs
76
+ - sig/zxcvbn/matchers/l33t.rbs
77
+ - sig/zxcvbn/matchers/regex_helpers.rbs
78
+ - sig/zxcvbn/matchers/repeat.rbs
79
+ - sig/zxcvbn/matchers/sequences.rbs
80
+ - sig/zxcvbn/matchers/spatial.rbs
81
+ - sig/zxcvbn/matchers/year.rbs
82
+ - sig/zxcvbn/math.rbs
83
+ - sig/zxcvbn/omnimatch.rbs
84
+ - sig/zxcvbn/score.rbs
85
+ - sig/zxcvbn/scorer.rbs
86
+ - sig/zxcvbn/tester.rbs
87
+ - sig/zxcvbn/tester_builder.rbs
88
+ - sig/zxcvbn/trie.rbs
89
+ homepage: https://github.com/envato/zxcvbn-ruby
58
90
  licenses:
59
91
  - MIT
60
92
  metadata:
93
+ allowed_push_host: https://rubygems.org
61
94
  bug_tracker_uri: https://github.com/envato/zxcvbn-ruby/issues
62
95
  changelog_uri: https://github.com/envato/zxcvbn-ruby/blob/HEAD/CHANGELOG.md
63
- documentation_uri: https://github.com/envato/zxcvbn-ruby/blob/HEAD/README.md
96
+ documentation_uri: https://www.rubydoc.info/gems/zxcvbn-ruby/2.0.0
64
97
  homepage_uri: https://github.com/envato/zxcvbn-ruby
65
- source_code_uri: https://github.com/envato/zxcvbn-ruby
98
+ source_code_uri: https://github.com/envato/zxcvbn-ruby/tree/v2.0.0
99
+ rubygems_mfa_required: 'true'
66
100
  rdoc_options: []
67
101
  require_paths:
68
102
  - lib
@@ -70,14 +104,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
70
104
  requirements:
71
105
  - - ">="
72
106
  - !ruby/object:Gem::Version
73
- version: '2.5'
107
+ version: '3.3'
74
108
  required_rubygems_version: !ruby/object:Gem::Requirement
75
109
  requirements:
76
110
  - - ">="
77
111
  - !ruby/object:Gem::Version
78
112
  version: '0'
79
113
  requirements: []
80
- rubygems_version: 4.0.3
114
+ rubygems_version: 4.0.12
81
115
  specification_version: 4
82
- summary: ''
116
+ summary: Ruby port of Dropbox's zxcvbn.js JavaScript password strength estimator
83
117
  test_files: []
@@ -1,158 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'zxcvbn/math'
4
-
5
- module Zxcvbn::Entropy
6
- include Zxcvbn::Math
7
-
8
- def calc_entropy(match)
9
- return match.entropy unless match.entropy.nil?
10
-
11
- match.entropy =
12
- case match.pattern
13
- when 'repeat'
14
- repeat_entropy(match)
15
- when 'sequence'
16
- sequence_entropy(match)
17
- when 'digits'
18
- digits_entropy(match)
19
- when 'year'
20
- year_entropy(match)
21
- when 'date'
22
- date_entropy(match)
23
- when 'spatial'
24
- spatial_entropy(match)
25
- when 'dictionary'
26
- dictionary_entropy(match)
27
- else
28
- 0
29
- end
30
- end
31
-
32
- def repeat_entropy(match)
33
- cardinality = bruteforce_cardinality match.token
34
- lg(cardinality * match.token.length)
35
- end
36
-
37
- def sequence_entropy(match)
38
- first_char = match.token[0]
39
- base_entropy =
40
- if ['a', '1'].include?(first_char)
41
- 1
42
- elsif first_char.match(/\d/)
43
- lg(10)
44
- elsif first_char.match(/[a-z]/)
45
- lg(26)
46
- else
47
- lg(26) + 1
48
- end
49
- base_entropy += 1 unless match.ascending
50
- base_entropy + lg(match.token.length)
51
- end
52
-
53
- def digits_entropy(match)
54
- lg(10**match.token.length)
55
- end
56
-
57
- NUM_YEARS = 119 # years match against 1900 - 2019
58
- NUM_MONTHS = 12
59
- NUM_DAYS = 31
60
-
61
- def year_entropy(_match)
62
- lg(NUM_YEARS)
63
- end
64
-
65
- def date_entropy(match)
66
- entropy =
67
- if match.year < 100
68
- lg(NUM_DAYS * NUM_MONTHS * 100)
69
- else
70
- lg(NUM_DAYS * NUM_MONTHS * NUM_YEARS)
71
- end
72
-
73
- entropy += 2 if match.separator
74
-
75
- entropy
76
- end
77
-
78
- def dictionary_entropy(match)
79
- match.base_entropy = lg(match.rank)
80
- match.uppercase_entropy = extra_uppercase_entropy(match)
81
- match.l33t_entropy = extra_l33t_entropy(match)
82
-
83
- match.base_entropy + match.uppercase_entropy + match.l33t_entropy
84
- end
85
-
86
- START_UPPER = /^[A-Z][^A-Z]+$/.freeze
87
- END_UPPER = /^[^A-Z]+[A-Z]$/.freeze
88
- ALL_UPPER = /^[A-Z]+$/.freeze
89
- ALL_LOWER = /^[a-z]+$/.freeze
90
-
91
- def extra_uppercase_entropy(match)
92
- word = match.token
93
- [START_UPPER, END_UPPER, ALL_UPPER].each do |regex|
94
- return 1 if word.match(regex)
95
- end
96
- num_upper = word.chars.count { |c| c.match(/[A-Z]/) }
97
- num_lower = word.chars.count { |c| c.match(/[a-z]/) }
98
- possibilities = 0
99
- (0..[num_upper, num_lower].min).each do |i|
100
- possibilities += nCk(num_upper + num_lower, i)
101
- end
102
- lg(possibilities)
103
- end
104
-
105
- def extra_l33t_entropy(match)
106
- word = match.token
107
- return 0 unless match.l33t
108
-
109
- possibilities = 0
110
- match.sub.each do |subbed, unsubbed|
111
- num_subbed = word.chars.count { |c| c == subbed }
112
- num_unsubbed = word.chars.count { |c| c == unsubbed }
113
- (0..[num_subbed, num_unsubbed].min).each do |i|
114
- possibilities += nCk(num_subbed + num_unsubbed, i)
115
- end
116
- end
117
- entropy = lg(possibilities)
118
- entropy.zero? ? 1 : entropy
119
- end
120
-
121
- def spatial_entropy(match)
122
- if %w[qwerty dvorak].include? match.graph
123
- starting_positions = starting_positions_for_graph('qwerty')
124
- average_degree = average_degree_for_graph('qwerty')
125
- else
126
- starting_positions = starting_positions_for_graph('keypad')
127
- average_degree = average_degree_for_graph('keypad')
128
- end
129
-
130
- possibilities = 0
131
- token_length = match.token.length
132
- turns = match.turns
133
-
134
- # estimate the ngpumber of possible patterns w/ token length or less with number of turns or less.
135
- (2..token_length).each do |i|
136
- possible_turns = [turns, i - 1].min
137
- (1..possible_turns).each do |j|
138
- possibilities += nCk(i - 1, j - 1) * starting_positions * average_degree**j
139
- end
140
- end
141
-
142
- entropy = lg possibilities
143
- # add extra entropy for shifted keys. (% instead of 5, A instead of a.)
144
- # math is similar to extra entropy from uppercase letters in dictionary matches.
145
-
146
- if match.shifted_count
147
- shiffted_count = match.shifted_count
148
- unshifted_count = match.token.length - match.shifted_count
149
- possibilities = 0
150
-
151
- (0..[shiffted_count, unshifted_count].min).each do |i|
152
- possibilities += nCk(shiffted_count + unshifted_count, i)
153
- end
154
- entropy += lg possibilities
155
- end
156
- entropy
157
- end
158
- end
@@ -1,118 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Zxcvbn
4
- module Matchers
5
- class L33t
6
- L33T_TABLE = {
7
- 'a' => ['4', '@'].freeze,
8
- 'b' => ['8'].freeze,
9
- 'c' => ['(', '{', '[', '<'].freeze,
10
- 'e' => ['3'].freeze,
11
- 'g' => ['6', '9'].freeze,
12
- 'i' => ['1', '!', '|'].freeze,
13
- 'l' => ['1', '|', '7'].freeze,
14
- 'o' => ['0'].freeze,
15
- 's' => ['$', '5'].freeze,
16
- 't' => ['+', '7'].freeze,
17
- 'x' => ['%'].freeze,
18
- 'z' => ['2'].freeze
19
- }.freeze
20
-
21
- def initialize(dictionary_matchers)
22
- @dictionary_matchers = dictionary_matchers
23
- end
24
-
25
- def matches(password)
26
- matches = []
27
- lowercased_password = password.downcase
28
- combinations_to_try = substitution_combinations(relevant_l33t_substitutions(lowercased_password))
29
- combinations_to_try.each do |substitutions|
30
- @dictionary_matchers.each do |matcher|
31
- subbed_password = substitute(lowercased_password, substitutions)
32
- matcher.matches(subbed_password).each do |match|
33
- length = match.j - match.i + 1
34
- token = lowercased_password.slice(match.i, length)
35
- next if token == match.matched_word.downcase
36
-
37
- match_substitutions = {}
38
- substitutions.each do |letter, substitution|
39
- match_substitutions[substitution] = letter if token.include?(substitution)
40
- end
41
- match.l33t = true
42
- match.token = password.slice(match.i, length)
43
- match.sub = match_substitutions
44
- match.sub_display = match_substitutions.map do |k, v|
45
- "#{k} -> #{v}"
46
- end.join(', ')
47
- matches << match
48
- end
49
- end
50
- end
51
- matches
52
- end
53
-
54
- def substitute(password, substitutions)
55
- subbed_password = password.dup
56
- substitutions.each do |letter, substitution|
57
- subbed_password.gsub!(substitution, letter)
58
- end
59
- subbed_password
60
- end
61
-
62
- # produces a l33t table of substitutions present in the given password
63
- def relevant_l33t_substitutions(password)
64
- subs = Hash.new do |hash, key|
65
- hash[key] = []
66
- end
67
- L33T_TABLE.each do |letter, substibutions|
68
- password.each_char do |password_char|
69
- subs[letter] << password_char if substibutions.include?(password_char)
70
- end
71
- end
72
- subs
73
- end
74
-
75
- # takes a character substitutions hash and produces an array of all
76
- # possible substitution combinations
77
- def substitution_combinations(subs_hash)
78
- combinations = []
79
- expanded_substitutions = expanded_substitutions(subs_hash)
80
-
81
- # build an array of all possible combinations
82
- expanded_substitutions.each do |substitution_hash|
83
- # convert a hash to an array of hashes with 1 key each
84
- subs_array = substitution_hash.map do |letter, substitutions|
85
- { letter => substitutions }
86
- end
87
- combinations << subs_array
88
-
89
- # find all possible combinations for each number of combinations available
90
- subs_array.combination(subs_array.size).each do |combination|
91
- # Don't add duplicates
92
- combinations << combination unless combinations.include?(combination)
93
- end
94
- end
95
-
96
- # convert back to simple hash per substitution combination
97
- combinations.map do |combination_set|
98
- hash = {}
99
- combination_set.each do |combination_hash|
100
- hash.merge!(combination_hash)
101
- end
102
- hash
103
- end
104
- end
105
-
106
- # expand possible combinations if multiple characters can be substituted
107
- # e.g. {'a' => ['4', '@'], 'i' => ['1']} expands to
108
- # [{'a' => '4', 'i' => 1}, {'a' => '@', 'i' => '1'}]
109
- def expanded_substitutions(hash)
110
- return {} if hash.empty?
111
-
112
- values = hash.values
113
- product_values = values[0].product(*values[1..-1])
114
- product_values.map { |p| Hash[hash.keys.zip(p)] }
115
- end
116
- end
117
- end
118
- end
@@ -1,27 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'zxcvbn/clock'
4
- require 'zxcvbn/feedback_giver'
5
- require 'zxcvbn/omnimatch'
6
- require 'zxcvbn/scorer'
7
-
8
- module Zxcvbn
9
- class PasswordStrength
10
- def initialize(data)
11
- @omnimatch = Omnimatch.new(data)
12
- @scorer = Scorer.new(data)
13
- end
14
-
15
- def test(password, user_inputs = [])
16
- password ||= ''
17
- result = nil
18
- calc_time = Clock.realtime do
19
- matches = @omnimatch.matches(password, user_inputs)
20
- result = @scorer.minimum_entropy_match_sequence(password, matches)
21
- end
22
- result.calc_time = calc_time
23
- result.feedback = FeedbackGiver.get_feedback(result.score, result.match_sequence)
24
- result
25
- end
26
- end
27
- end