zxcvbn-ruby 1.2.1 → 1.2.2
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 +9 -1
- data/lib/zxcvbn/clock.rb +2 -1
- data/lib/zxcvbn/crack_time.rb +4 -2
- data/lib/zxcvbn/data.rb +6 -4
- data/lib/zxcvbn/dictionary_ranker.rb +2 -0
- data/lib/zxcvbn/entropy.rb +38 -33
- data/lib/zxcvbn/feedback.rb +2 -0
- data/lib/zxcvbn/feedback_giver.rb +2 -0
- data/lib/zxcvbn/match.rb +2 -0
- data/lib/zxcvbn/matchers/date.rb +11 -8
- data/lib/zxcvbn/matchers/dictionary.rb +3 -1
- data/lib/zxcvbn/matchers/digits.rb +2 -0
- data/lib/zxcvbn/matchers/l33t.rb +6 -2
- data/lib/zxcvbn/matchers/new_l33t.rb +7 -4
- data/lib/zxcvbn/matchers/regex_helpers.rb +2 -0
- data/lib/zxcvbn/matchers/repeat.rb +3 -1
- data/lib/zxcvbn/matchers/sequences.rb +4 -2
- data/lib/zxcvbn/matchers/spatial.rb +5 -3
- data/lib/zxcvbn/matchers/year.rb +3 -1
- data/lib/zxcvbn/math.rb +3 -0
- data/lib/zxcvbn/omnimatch.rb +3 -1
- data/lib/zxcvbn/password_strength.rb +2 -0
- data/lib/zxcvbn/score.rb +3 -1
- data/lib/zxcvbn/scorer.rb +11 -7
- data/lib/zxcvbn/version.rb +1 -1
- data/lib/zxcvbn.rb +2 -0
- metadata +2 -30
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 7132f3b910a1849c47f1edcb52d2dd2f6f3954472ad912bd20520c942fbc0bda
|
|
4
|
+
data.tar.gz: 45c893886c18c81fcb03627b7dd341eecab816a5fab4e7a9efb9ab5ca0ff9375
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 259633b2081be2ddd35ae1dfbcf17cf44e50f099e2bff7accbe15ffc6f1faa92217517a6ffdb060540276170f1a12d4cb8fab3ef256ee152efc525b6a902d3c2
|
|
7
|
+
data.tar.gz: 73e728a75b41c46e87074a95a7a8b1849eb6fcb50899e3b3bee6900d77ca0ecacd85f7d2ed539a180420423f0a6d705dc9da377bce107db5e21cb7f661682230
|
data/CHANGELOG.md
CHANGED
|
@@ -6,7 +6,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
-
[Unreleased]: https://github.com/envato/zxcvbn-ruby/compare/v1.2.
|
|
9
|
+
[Unreleased]: https://github.com/envato/zxcvbn-ruby/compare/v1.2.2...HEAD
|
|
10
|
+
|
|
11
|
+
## [1.2.2] - 2025-12-06
|
|
12
|
+
|
|
13
|
+
### Changed
|
|
14
|
+
- Address layout and frozen string literal issues ([#49])
|
|
15
|
+
|
|
16
|
+
[1.2.2]: https://github.com/envato/zxcvbn-ruby/compare/v1.2.1...v.1.2.2
|
|
17
|
+
[#49]: https://github.com/envato/zxcvbn-ruby/pull/49
|
|
10
18
|
|
|
11
19
|
## [1.2.1] - 2025-12-05
|
|
12
20
|
|
data/lib/zxcvbn/clock.rb
CHANGED
data/lib/zxcvbn/crack_time.rb
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module Zxcvbn
|
|
2
4
|
module CrackTime
|
|
3
5
|
SINGLE_GUESS = 0.010
|
|
@@ -6,7 +8,7 @@ module Zxcvbn
|
|
|
6
8
|
SECONDS_PER_GUESS = SINGLE_GUESS / NUM_ATTACKERS
|
|
7
9
|
|
|
8
10
|
def entropy_to_crack_time(entropy)
|
|
9
|
-
0.5 * (2
|
|
11
|
+
0.5 * (2**entropy) * SECONDS_PER_GUESS
|
|
10
12
|
end
|
|
11
13
|
|
|
12
14
|
def crack_time_to_score(seconds)
|
|
@@ -50,4 +52,4 @@ module Zxcvbn
|
|
|
50
52
|
end
|
|
51
53
|
end
|
|
52
54
|
end
|
|
53
|
-
end
|
|
55
|
+
end
|
data/lib/zxcvbn/data.rb
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require 'json'
|
|
2
4
|
require 'zxcvbn/dictionary_ranker'
|
|
3
5
|
|
|
@@ -5,11 +7,11 @@ module Zxcvbn
|
|
|
5
7
|
class Data
|
|
6
8
|
def initialize
|
|
7
9
|
@ranked_dictionaries = DictionaryRanker.rank_dictionaries(
|
|
8
|
-
"english" =>
|
|
10
|
+
"english" => read_word_list("english.txt"),
|
|
9
11
|
"female_names" => read_word_list("female_names.txt"),
|
|
10
|
-
"male_names" =>
|
|
11
|
-
"passwords" =>
|
|
12
|
-
"surnames" =>
|
|
12
|
+
"male_names" => read_word_list("male_names.txt"),
|
|
13
|
+
"passwords" => read_word_list("passwords.txt"),
|
|
14
|
+
"surnames" => read_word_list("surnames.txt")
|
|
13
15
|
)
|
|
14
16
|
@adjacency_graphs = JSON.load(DATA_PATH.join('adjacency_graphs.json').read)
|
|
15
17
|
end
|
data/lib/zxcvbn/entropy.rb
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require 'zxcvbn/math'
|
|
2
4
|
|
|
3
5
|
module Zxcvbn::Entropy
|
|
@@ -6,24 +8,25 @@ module Zxcvbn::Entropy
|
|
|
6
8
|
def calc_entropy(match)
|
|
7
9
|
return match.entropy unless match.entropy.nil?
|
|
8
10
|
|
|
9
|
-
match.entropy =
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
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
|
|
27
30
|
end
|
|
28
31
|
|
|
29
32
|
def repeat_entropy(match)
|
|
@@ -33,21 +36,22 @@ module Zxcvbn::Entropy
|
|
|
33
36
|
|
|
34
37
|
def sequence_entropy(match)
|
|
35
38
|
first_char = match.token[0]
|
|
36
|
-
base_entropy =
|
|
37
|
-
1
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
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
|
|
45
49
|
base_entropy += 1 unless match.ascending
|
|
46
50
|
base_entropy + lg(match.token.length)
|
|
47
51
|
end
|
|
48
52
|
|
|
49
53
|
def digits_entropy(match)
|
|
50
|
-
lg(10
|
|
54
|
+
lg(10**match.token.length)
|
|
51
55
|
end
|
|
52
56
|
|
|
53
57
|
NUM_YEARS = 119 # years match against 1900 - 2019
|
|
@@ -90,8 +94,8 @@ module Zxcvbn::Entropy
|
|
|
90
94
|
[START_UPPER, END_UPPER, ALL_UPPER].each do |regex|
|
|
91
95
|
return 1 if word.match(regex)
|
|
92
96
|
end
|
|
93
|
-
num_upper = word.chars.count{|c| c.match(/[A-Z]/) }
|
|
94
|
-
num_lower = word.chars.count{|c| c.match(/[a-z]/) }
|
|
97
|
+
num_upper = word.chars.count { |c| c.match(/[A-Z]/) }
|
|
98
|
+
num_lower = word.chars.count { |c| c.match(/[a-z]/) }
|
|
95
99
|
possibilities = 0
|
|
96
100
|
(0..[num_upper, num_lower].min).each do |i|
|
|
97
101
|
possibilities += nCk(num_upper + num_lower, i)
|
|
@@ -102,10 +106,11 @@ module Zxcvbn::Entropy
|
|
|
102
106
|
def extra_l33t_entropy(match)
|
|
103
107
|
word = match.token
|
|
104
108
|
return 0 unless match.l33t
|
|
109
|
+
|
|
105
110
|
possibilities = 0
|
|
106
111
|
match.sub.each do |subbed, unsubbed|
|
|
107
|
-
num_subbed = word.chars.count{|c| c == subbed}
|
|
108
|
-
num_unsubbed = word.chars.count{|c| c == unsubbed}
|
|
112
|
+
num_subbed = word.chars.count { |c| c == subbed }
|
|
113
|
+
num_unsubbed = word.chars.count { |c| c == unsubbed }
|
|
109
114
|
(0..[num_subbed, num_unsubbed].min).each do |i|
|
|
110
115
|
possibilities += nCk(num_subbed + num_unsubbed, i)
|
|
111
116
|
end
|
|
@@ -131,7 +136,7 @@ module Zxcvbn::Entropy
|
|
|
131
136
|
(2..token_length).each do |i|
|
|
132
137
|
possible_turns = [turns, i - 1].min
|
|
133
138
|
(1..possible_turns).each do |j|
|
|
134
|
-
possibilities += nCk(i - 1, j - 1) * starting_positions * average_degree
|
|
139
|
+
possibilities += nCk(i - 1, j - 1) * starting_positions * average_degree**j
|
|
135
140
|
end
|
|
136
141
|
end
|
|
137
142
|
|
data/lib/zxcvbn/feedback.rb
CHANGED
data/lib/zxcvbn/match.rb
CHANGED
data/lib/zxcvbn/matchers/date.rb
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require 'zxcvbn/matchers/regex_helpers'
|
|
2
4
|
|
|
3
5
|
module Zxcvbn
|
|
@@ -71,9 +73,9 @@ module Zxcvbn
|
|
|
71
73
|
dates = []
|
|
72
74
|
date_patterns_for_length(token.length).map do |pattern|
|
|
73
75
|
candidate = {
|
|
74
|
-
:year => '',
|
|
75
|
-
:month => '',
|
|
76
|
-
:day => ''
|
|
76
|
+
:year => +'',
|
|
77
|
+
:month => +'',
|
|
78
|
+
:day => +''
|
|
77
79
|
}
|
|
78
80
|
for i in 0...token.length
|
|
79
81
|
candidate[PATTERN_CHAR_TO_SYM[pattern[i]]] << token[i]
|
|
@@ -92,11 +94,11 @@ module Zxcvbn
|
|
|
92
94
|
end
|
|
93
95
|
|
|
94
96
|
DATE_PATTERN_FOR_LENGTH = {
|
|
95
|
-
8 => %w[
|
|
96
|
-
7 => %w[
|
|
97
|
-
6 => %w[
|
|
98
|
-
5 => %w[
|
|
99
|
-
4 => %w[
|
|
97
|
+
8 => %w[yyyymmdd ddmmyyyy mmddyyyy],
|
|
98
|
+
7 => %w[yyyymdd yyyymmd ddmyyyy dmmyyyy],
|
|
99
|
+
6 => %w[yymmdd ddmmyy mmddyy],
|
|
100
|
+
5 => %w[yymdd yymmd ddmyy dmmyy mmdyy mddyy],
|
|
101
|
+
4 => %w[yymd dmyy mdyy]
|
|
100
102
|
}
|
|
101
103
|
|
|
102
104
|
PATTERN_CHAR_TO_SYM = {
|
|
@@ -112,6 +114,7 @@ module Zxcvbn
|
|
|
112
114
|
def valid_date?(day, month, year)
|
|
113
115
|
return false if day > 31 || month > 12
|
|
114
116
|
return false unless year >= 1900 && year <= 2019
|
|
117
|
+
|
|
115
118
|
true
|
|
116
119
|
end
|
|
117
120
|
|
data/lib/zxcvbn/matchers/l33t.rb
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module Zxcvbn
|
|
2
4
|
module Matchers
|
|
3
5
|
class L33t
|
|
@@ -30,6 +32,7 @@ module Zxcvbn
|
|
|
30
32
|
matcher.matches(subbed_password).each do |match|
|
|
31
33
|
token = password[match.i..match.j]
|
|
32
34
|
next if token.downcase == match.matched_word.downcase
|
|
35
|
+
|
|
33
36
|
match_substitutions = {}
|
|
34
37
|
substitution.each do |s, letter|
|
|
35
38
|
match_substitutions[s] = letter if token.include?(s)
|
|
@@ -79,6 +82,7 @@ module Zxcvbn
|
|
|
79
82
|
|
|
80
83
|
def find_substitutions(subs, table, keys)
|
|
81
84
|
return subs if keys.empty?
|
|
85
|
+
|
|
82
86
|
first_key = keys[0]
|
|
83
87
|
rest_keys = keys[1..-1]
|
|
84
88
|
next_subs = []
|
|
@@ -114,7 +118,7 @@ module Zxcvbn
|
|
|
114
118
|
assoc = sub.dup
|
|
115
119
|
|
|
116
120
|
assoc.sort! rescue debugger
|
|
117
|
-
label = assoc.map{|k, v| "#{k},#{v}"}.join('-')
|
|
121
|
+
label = assoc.map { |k, v| "#{k},#{v}" }.join('-')
|
|
118
122
|
unless members.include?(label)
|
|
119
123
|
members << label
|
|
120
124
|
deduped << sub
|
|
@@ -124,4 +128,4 @@ module Zxcvbn
|
|
|
124
128
|
end
|
|
125
129
|
end
|
|
126
130
|
end
|
|
127
|
-
end
|
|
131
|
+
end
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module Zxcvbn
|
|
2
4
|
module Matchers
|
|
3
5
|
class L33t
|
|
@@ -31,6 +33,7 @@ module Zxcvbn
|
|
|
31
33
|
matcher.matches(subbed_password).each do |match|
|
|
32
34
|
token = lowercased_password[match.i..match.j]
|
|
33
35
|
next if token == match.matched_word.downcase
|
|
36
|
+
|
|
34
37
|
# debugger if token == '1'
|
|
35
38
|
match_substitutions = {}
|
|
36
39
|
substitution.each do |letter, substitution|
|
|
@@ -82,7 +85,7 @@ module Zxcvbn
|
|
|
82
85
|
expanded_substitutions.each do |substitution_hash|
|
|
83
86
|
# convert a hash to an array of hashes with 1 key each
|
|
84
87
|
subs_array = substitution_hash.map do |letter, substitutions|
|
|
85
|
-
{letter => substitutions}
|
|
88
|
+
{ letter => substitutions }
|
|
86
89
|
end
|
|
87
90
|
combinations << subs_array
|
|
88
91
|
|
|
@@ -110,11 +113,11 @@ module Zxcvbn
|
|
|
110
113
|
# [{'a' => '4', 'i' => 1}, {'a' => '@', 'i' => '1'}]
|
|
111
114
|
def expanded_substitutions(hash)
|
|
112
115
|
return {} if hash.empty?
|
|
116
|
+
|
|
113
117
|
values = hash.values
|
|
114
118
|
product_values = values[0].product(*values[1..-1])
|
|
115
|
-
product_values.map{ |p| Hash[hash.keys.zip(p)] }
|
|
119
|
+
product_values.map { |p| Hash[hash.keys.zip(p)] }
|
|
116
120
|
end
|
|
117
|
-
|
|
118
121
|
end
|
|
119
122
|
end
|
|
120
|
-
end
|
|
123
|
+
end
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require 'zxcvbn/match'
|
|
2
4
|
|
|
3
5
|
module Zxcvbn
|
|
@@ -17,7 +19,7 @@ module Zxcvbn
|
|
|
17
19
|
result << Match.new(
|
|
18
20
|
:pattern => 'repeat',
|
|
19
21
|
:i => i,
|
|
20
|
-
:j => j-1,
|
|
22
|
+
:j => j - 1,
|
|
21
23
|
:token => password[i...j],
|
|
22
24
|
:repeated_char => cur_char
|
|
23
25
|
)
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require 'zxcvbn/match'
|
|
2
4
|
|
|
3
5
|
module Zxcvbn
|
|
@@ -14,7 +16,7 @@ module Zxcvbn
|
|
|
14
16
|
j = 1
|
|
15
17
|
while from + j < password.length &&
|
|
16
18
|
password[from + j] == seq[index_from + direction * j]
|
|
17
|
-
j+= 1
|
|
19
|
+
j += 1
|
|
18
20
|
end
|
|
19
21
|
j
|
|
20
22
|
end
|
|
@@ -24,7 +26,7 @@ module Zxcvbn
|
|
|
24
26
|
def applicable_sequence(password, i)
|
|
25
27
|
SEQUENCES.each do |name, sequence|
|
|
26
28
|
index1 = sequence.index(password[i])
|
|
27
|
-
index2 = sequence.index(password[i+1])
|
|
29
|
+
index2 = sequence.index(password[i + 1])
|
|
28
30
|
if index1 and index2
|
|
29
31
|
seq_direction = index2 - index1
|
|
30
32
|
if [-1, 1].include?(seq_direction)
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require 'zxcvbn/match'
|
|
2
4
|
|
|
3
5
|
module Zxcvbn
|
|
@@ -24,7 +26,7 @@ module Zxcvbn
|
|
|
24
26
|
turns = 0
|
|
25
27
|
shifted_count = 0
|
|
26
28
|
loop do
|
|
27
|
-
prev_char = password[j-1]
|
|
29
|
+
prev_char = password[j - 1]
|
|
28
30
|
found = false
|
|
29
31
|
found_direction = -1
|
|
30
32
|
cur_direction = -1
|
|
@@ -61,7 +63,7 @@ module Zxcvbn
|
|
|
61
63
|
result << Match.new(
|
|
62
64
|
:pattern => 'spatial',
|
|
63
65
|
:i => i,
|
|
64
|
-
:j => j-1,
|
|
66
|
+
:j => j - 1,
|
|
65
67
|
:token => password[i...j],
|
|
66
68
|
:graph => graph_name,
|
|
67
69
|
:turns => turns,
|
|
@@ -78,4 +80,4 @@ module Zxcvbn
|
|
|
78
80
|
end
|
|
79
81
|
end
|
|
80
82
|
end
|
|
81
|
-
end
|
|
83
|
+
end
|
data/lib/zxcvbn/matchers/year.rb
CHANGED
data/lib/zxcvbn/math.rb
CHANGED
data/lib/zxcvbn/omnimatch.rb
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require 'zxcvbn/dictionary_ranker'
|
|
2
4
|
require 'zxcvbn/matchers/dictionary'
|
|
3
5
|
require 'zxcvbn/matchers/l33t'
|
|
@@ -26,13 +28,13 @@ module Zxcvbn
|
|
|
26
28
|
|
|
27
29
|
def user_input_matchers(user_inputs)
|
|
28
30
|
return [] unless user_inputs.any?
|
|
31
|
+
|
|
29
32
|
user_ranked_dictionary = DictionaryRanker.rank_dictionary(user_inputs)
|
|
30
33
|
dictionary_matcher = Matchers::Dictionary.new('user_inputs', user_ranked_dictionary)
|
|
31
34
|
l33t_matcher = Matchers::L33t.new([dictionary_matcher])
|
|
32
35
|
[dictionary_matcher, l33t_matcher]
|
|
33
36
|
end
|
|
34
37
|
|
|
35
|
-
|
|
36
38
|
def build_matchers
|
|
37
39
|
matchers = []
|
|
38
40
|
dictionary_matchers = @data.ranked_dictionaries.map do |name, dictionary|
|
data/lib/zxcvbn/score.rb
CHANGED
data/lib/zxcvbn/scorer.rb
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require 'zxcvbn/entropy'
|
|
2
4
|
require 'zxcvbn/crack_time'
|
|
3
5
|
require 'zxcvbn/score'
|
|
@@ -16,11 +18,13 @@ module Zxcvbn
|
|
|
16
18
|
|
|
17
19
|
def minimum_entropy_match_sequence(password, matches)
|
|
18
20
|
bruteforce_cardinality = bruteforce_cardinality(password) # e.g. 26 for lowercase
|
|
19
|
-
up_to_k = []
|
|
20
|
-
|
|
21
|
+
up_to_k = [] # minimum entropy up to k.
|
|
22
|
+
# for the optimal sequence of matches up to k, holds the final match (match.j == k).
|
|
23
|
+
# null means the sequence ends w/ a brute-force character.
|
|
24
|
+
backpointers = []
|
|
21
25
|
(0...password.length).each do |k|
|
|
22
26
|
# starting scenario to try and beat: adding a brute-force character to the minimum entropy sequence at k-1.
|
|
23
|
-
previous_k_entropy = k > 0 ? up_to_k[k-1] : 0
|
|
27
|
+
previous_k_entropy = k > 0 ? up_to_k[k - 1] : 0
|
|
24
28
|
up_to_k[k] = previous_k_entropy + lg(bruteforce_cardinality)
|
|
25
29
|
backpointers[k] = nil
|
|
26
30
|
matches.select do |match|
|
|
@@ -28,7 +32,7 @@ module Zxcvbn
|
|
|
28
32
|
end.each do |match|
|
|
29
33
|
i, j = match.i, match.j
|
|
30
34
|
# see if best entropy up to i-1 + entropy of this match is less than the current minimum at j.
|
|
31
|
-
previous_i_entropy = i > 0 ? up_to_k[i-1] : 0
|
|
35
|
+
previous_i_entropy = i > 0 ? up_to_k[i - 1] : 0
|
|
32
36
|
candidate_entropy = previous_i_entropy + calc_entropy(match)
|
|
33
37
|
if up_to_k[j] && candidate_entropy < up_to_k[j]
|
|
34
38
|
up_to_k[j] = candidate_entropy
|
|
@@ -55,7 +59,7 @@ module Zxcvbn
|
|
|
55
59
|
end
|
|
56
60
|
|
|
57
61
|
def score_for password, match_sequence, up_to_k
|
|
58
|
-
min_entropy = up_to_k[password.length - 1] || 0
|
|
62
|
+
min_entropy = up_to_k[password.length - 1] || 0 # or 0 corner case is for an empty password ''
|
|
59
63
|
crack_time = entropy_to_crack_time(min_entropy)
|
|
60
64
|
|
|
61
65
|
# final result object
|
|
@@ -69,7 +73,6 @@ module Zxcvbn
|
|
|
69
73
|
)
|
|
70
74
|
end
|
|
71
75
|
|
|
72
|
-
|
|
73
76
|
def pad_with_bruteforce_matches(match_sequence, password, bruteforce_cardinality)
|
|
74
77
|
k = 0
|
|
75
78
|
match_sequence_copy = []
|
|
@@ -85,6 +88,7 @@ module Zxcvbn
|
|
|
85
88
|
end
|
|
86
89
|
match_sequence_copy
|
|
87
90
|
end
|
|
91
|
+
|
|
88
92
|
# fill in the blanks between pattern matches with bruteforce "matches"
|
|
89
93
|
# that way the match sequence fully covers the password:
|
|
90
94
|
# match1.j == match2.i - 1 for every adjacent match1, match2.
|
|
@@ -94,7 +98,7 @@ module Zxcvbn
|
|
|
94
98
|
:i => i,
|
|
95
99
|
:j => j,
|
|
96
100
|
:token => password[i..j],
|
|
97
|
-
:entropy => lg(bruteforce_cardinality
|
|
101
|
+
:entropy => lg(bruteforce_cardinality**(j - i + 1)),
|
|
98
102
|
:cardinality => bruteforce_cardinality
|
|
99
103
|
)
|
|
100
104
|
end
|
data/lib/zxcvbn/version.rb
CHANGED
data/lib/zxcvbn.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: zxcvbn-ruby
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.2.
|
|
4
|
+
version: 1.2.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Steve Hodgkiss
|
|
@@ -9,35 +9,7 @@ authors:
|
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
11
|
date: 1980-01-02 00:00:00.000000000 Z
|
|
12
|
-
dependencies:
|
|
13
|
-
- !ruby/object:Gem::Dependency
|
|
14
|
-
name: mini_racer
|
|
15
|
-
requirement: !ruby/object:Gem::Requirement
|
|
16
|
-
requirements:
|
|
17
|
-
- - ">="
|
|
18
|
-
- !ruby/object:Gem::Version
|
|
19
|
-
version: '0'
|
|
20
|
-
type: :development
|
|
21
|
-
prerelease: false
|
|
22
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
-
requirements:
|
|
24
|
-
- - ">="
|
|
25
|
-
- !ruby/object:Gem::Version
|
|
26
|
-
version: '0'
|
|
27
|
-
- !ruby/object:Gem::Dependency
|
|
28
|
-
name: rspec
|
|
29
|
-
requirement: !ruby/object:Gem::Requirement
|
|
30
|
-
requirements:
|
|
31
|
-
- - ">="
|
|
32
|
-
- !ruby/object:Gem::Version
|
|
33
|
-
version: '0'
|
|
34
|
-
type: :development
|
|
35
|
-
prerelease: false
|
|
36
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
-
requirements:
|
|
38
|
-
- - ">="
|
|
39
|
-
- !ruby/object:Gem::Version
|
|
40
|
-
version: '0'
|
|
12
|
+
dependencies: []
|
|
41
13
|
description: Ruby port of Dropboxs zxcvbn.js
|
|
42
14
|
email:
|
|
43
15
|
- steve@hodgkiss.me
|