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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +71 -1
- data/README.md +322 -75
- data/data/frequency_lists/english_wikipedia.txt +30000 -0
- data/data/frequency_lists/female_names.txt +11 -114
- data/data/frequency_lists/male_names.txt +3 -24
- data/data/frequency_lists/passwords.txt +29623 -6764
- data/data/frequency_lists/surnames.txt +28 -30611
- data/data/frequency_lists/{english.txt → us_tv_and_film.txt} +147 -13532
- data/lib/zxcvbn/clock.rb +6 -0
- data/lib/zxcvbn/crack_time.rb +52 -18
- data/lib/zxcvbn/data.rb +61 -21
- data/lib/zxcvbn/dictionary_ranker.rb +10 -0
- data/lib/zxcvbn/feedback.rb +11 -6
- data/lib/zxcvbn/feedback_giver.rb +75 -50
- data/lib/zxcvbn/guesses.rb +208 -0
- data/lib/zxcvbn/match.rb +95 -15
- data/lib/zxcvbn/match_builder.rb +15 -0
- data/lib/zxcvbn/matchers/date.rb +171 -106
- data/lib/zxcvbn/matchers/dictionary.rb +15 -8
- data/lib/zxcvbn/matchers/digits.rb +6 -1
- data/lib/zxcvbn/matchers/l33t.rb +30 -34
- data/lib/zxcvbn/matchers/regex_helpers.rb +14 -6
- data/lib/zxcvbn/matchers/repeat.rb +47 -16
- data/lib/zxcvbn/matchers/sequences.rb +58 -48
- data/lib/zxcvbn/matchers/spatial.rb +22 -6
- data/lib/zxcvbn/matchers/year.rb +6 -1
- data/lib/zxcvbn/math.rb +15 -28
- data/lib/zxcvbn/omnimatch.rb +70 -22
- data/lib/zxcvbn/ruby.rb +3 -0
- data/lib/zxcvbn/score.rb +34 -10
- data/lib/zxcvbn/scorer.rb +142 -75
- data/lib/zxcvbn/tester.rb +58 -23
- data/lib/zxcvbn/tester_builder.rb +83 -0
- data/lib/zxcvbn/trie.rb +21 -0
- data/lib/zxcvbn/version.rb +1 -1
- data/lib/zxcvbn.rb +47 -7
- data/sig/README.md +65 -0
- data/sig/zxcvbn/clock.rbs +5 -0
- data/sig/zxcvbn/crack_time.rbs +11 -0
- data/sig/zxcvbn/data.rbs +40 -0
- data/sig/zxcvbn/dictionary_ranker.rbs +7 -0
- data/sig/zxcvbn/feedback.rbs +10 -0
- data/sig/zxcvbn/feedback_giver.rbs +13 -0
- data/sig/zxcvbn/guesses.rbs +36 -0
- data/sig/zxcvbn/match.rbs +40 -0
- data/sig/zxcvbn/match_builder.rbs +36 -0
- data/sig/zxcvbn/matchers/date.rbs +23 -0
- data/sig/zxcvbn/matchers/dictionary.rbs +21 -0
- data/sig/zxcvbn/matchers/digits.rbs +11 -0
- data/sig/zxcvbn/matchers/l33t.rbs +27 -0
- data/sig/zxcvbn/matchers/regex_helpers.rbs +7 -0
- data/sig/zxcvbn/matchers/repeat.rbs +11 -0
- data/sig/zxcvbn/matchers/sequences.rbs +16 -0
- data/sig/zxcvbn/matchers/spatial.rbs +15 -0
- data/sig/zxcvbn/matchers/year.rbs +11 -0
- data/sig/zxcvbn/math.rbs +9 -0
- data/sig/zxcvbn/omnimatch.rbs +19 -0
- data/sig/zxcvbn/score.rbs +26 -0
- data/sig/zxcvbn/scorer.rbs +19 -0
- data/sig/zxcvbn/tester.rbs +15 -0
- data/sig/zxcvbn/tester_builder.rbs +16 -0
- data/sig/zxcvbn/trie.rbs +17 -0
- data/sig/zxcvbn.rbs +12 -0
- metadata +46 -12
- data/lib/zxcvbn/entropy.rb +0 -158
- data/lib/zxcvbn/matchers/new_l33t.rb +0 -118
- 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:
|
|
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
|
|
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/
|
|
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/
|
|
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
|
-
|
|
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://
|
|
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: '
|
|
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.
|
|
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: []
|
data/lib/zxcvbn/entropy.rb
DELETED
|
@@ -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
|