strong_password 0.0.6 → 0.0.8
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 +14 -1
- data/README.md +19 -17
- data/lib/active_model/validations/password_strength_validator.rb +2 -2
- data/lib/strong_password/dictionary_adjuster.rb +15 -11
- data/lib/strong_password/qwerty_adjuster.rb +35 -31
- data/lib/strong_password/strength_checker.rb +34 -16
- data/lib/strong_password/version.rb +1 -1
- data/spec/strong_password/dictionary_adjuster_spec.rb +16 -16
- data/spec/strong_password/entropy_calculator_spec.rb +1 -1
- data/spec/strong_password/nist_bonus_bits_spec.rb +3 -3
- data/spec/strong_password/password_variants_spec.rb +2 -2
- data/spec/strong_password/qwerty_adjuster_spec.rb +10 -10
- data/spec/strong_password/strength_checker_spec.rb +12 -9
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9a5e366ad0a5a46d9b2dbda6c2b637bfcbc667f4ed8e7b4cd25e64ed75d315bd
|
4
|
+
data.tar.gz: 2793f8c780ef37d19a3b5b11ae81adf175fa0f2d6716047830e0d79efbc6891c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6a155d97437572fcaa4b17142f1f0e355128925620dd461525f8480194f6b9c89dc848494d2eb261b9cca15c54cbd452437ad6f2a3f8f4f4ccccbb9bb9955bb9
|
7
|
+
data.tar.gz: 63761deb2f831eed7cf7150c17f72e9f64f5d37398ce7ff1a9c253a61404775654fa0f29113af367f43c3cc2471d025f099e15083c06b0ef9c88206fbdd22a42
|
data/CHANGELOG
CHANGED
@@ -1,3 +1,16 @@
|
|
1
|
+
## v0.0.8
|
2
|
+
This release breaks backwards compatibility if you are using the lib components
|
3
|
+
directly. If you are using the ActiveModel validations in a Rails project then
|
4
|
+
you should be unaffected.
|
5
|
+
|
6
|
+
The major changes when used directly are that you now initialize the objects
|
7
|
+
with the various strength options instead of the password and then provide the
|
8
|
+
password to the various methods. The README section on standalone usage has
|
9
|
+
been updated to reflect this change.
|
10
|
+
|
11
|
+
* jacobat - Performance improvements
|
12
|
+
* jacksenechal - Updated README
|
13
|
+
|
1
14
|
## v0.0.4
|
2
15
|
* peterkovacs - Increase size of password dictionary
|
3
16
|
* peterkovacs - Swapped in a cleaner, more efficient comparison algorithm
|
@@ -7,4 +20,4 @@
|
|
7
20
|
|
8
21
|
## v0.0.1
|
9
22
|
|
10
|
-
* Initial release
|
23
|
+
* Initial release
|
data/README.md
CHANGED
@@ -19,7 +19,7 @@ NOTE: StrongPassword requires the use of Ruby 2.0. Upgrade if you haven't alrea
|
|
19
19
|
|
20
20
|
Add this line to your application's Gemfile:
|
21
21
|
|
22
|
-
gem 'strong_password', '~> 0.0.
|
22
|
+
gem 'strong_password', '~> 0.0.8'
|
23
23
|
|
24
24
|
And then execute:
|
25
25
|
|
@@ -82,18 +82,22 @@ StrongPassword can also be used standalone if you need to. There are a few helpe
|
|
82
82
|
password is strong or not. You can also directly access the entropy calculations if you want.
|
83
83
|
|
84
84
|
```text
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
85
|
+
irb(main):004:0> checker = StrongPassword::StrengthChecker.new
|
86
|
+
=> #<StrongPassword::StrengthChecker:0x00007f985509db30 @min_entropy=18, @use_dictionary=false, @min_word_length=4, @extra_dictionary_words=[]>
|
87
|
+
irb(main):005:0> checker.is_strong?("password")
|
88
|
+
=> false
|
89
|
+
irb(main):006:0> checker.is_weak?("password")
|
90
|
+
=> true
|
91
|
+
irb(main):007:0> checker = StrongPassword::StrengthChecker.new(min_entropy: 2)
|
92
|
+
=> #<StrongPassword::StrengthChecker:0x00007f9855147bd0 @min_entropy=2, @use_dictionary=false, @min_word_length=4, @extra_dictionary_words=[]>
|
93
|
+
irb(main):008:0> checker.calculate_entropy("password")
|
94
|
+
=> 15.5
|
95
|
+
irb(main):009:0> checker.is_strong?("password")
|
96
|
+
=> true
|
97
|
+
irb(main):010:0> checker = StrongPassword::StrengthChecker.new(use_dictionary: true)
|
98
|
+
=> #<StrongPassword::StrengthChecker:0x00007f98550ee008 @min_entropy=18, @use_dictionary=true, @min_word_length=4, @extra_dictionary_words=[]>
|
99
|
+
irb(main):011:0> checker.calculate_entropy("password")
|
100
|
+
=> 2
|
97
101
|
```
|
98
102
|
|
99
103
|
## Details
|
@@ -145,11 +149,9 @@ disallowed by the strength checker.
|
|
145
149
|
|
146
150
|
## Todo
|
147
151
|
|
148
|
-
1.
|
149
|
-
the 500 most common passwords. That also means it's not a true "dictionary" check...
|
150
|
-
2. Add a common password adjuster that basically works like the existing DictionaryAdjuster but does
|
152
|
+
1. Add a common password adjuster that basically works like the existing DictionaryAdjuster but does
|
151
153
|
not stop at the first found word. Stopping at the first word make sense if you have a 300,000 word
|
152
|
-
dictionary of the English language but not so much when you're only talking about the
|
154
|
+
dictionary of the English language but not so much when you're only talking about the 10,000 most
|
153
155
|
common passwords.
|
154
156
|
|
155
157
|
## Running the tests
|
@@ -4,8 +4,8 @@ module ActiveModel
|
|
4
4
|
module Validations
|
5
5
|
class PasswordStrengthValidator < ActiveModel::EachValidator
|
6
6
|
def validate_each(object, attribute, value)
|
7
|
-
ps = ::StrongPassword::StrengthChecker.new(
|
8
|
-
unless ps.is_strong?(
|
7
|
+
ps = ::StrongPassword::StrengthChecker.new(strength_options(options, object))
|
8
|
+
unless ps.is_strong?(value.to_s)
|
9
9
|
object.errors.add(attribute, :'password.password_strength', options.merge(:value => value.to_s))
|
10
10
|
end
|
11
11
|
end
|
@@ -967,20 +967,20 @@ module StrongPassword
|
|
967
967
|
hounds honeydew hooters1 hoes howie hevnm4 hugohugo eighty epson evangeli
|
968
968
|
eeeee1 eyphed ).sort_by(&:length).reverse
|
969
969
|
|
970
|
-
attr_reader :
|
970
|
+
attr_reader :min_entropy, :min_word_length, :extra_dictionary_words
|
971
971
|
|
972
|
-
def initialize(
|
973
|
-
@
|
972
|
+
def initialize(min_entropy: 18, min_word_length: 4, extra_dictionary_words: [])
|
973
|
+
@min_entropy = min_entropy
|
974
|
+
@min_word_length = min_word_length
|
975
|
+
@extra_dictionary_words = extra_dictionary_words
|
974
976
|
end
|
975
977
|
|
976
|
-
def is_strong?(
|
977
|
-
adjusted_entropy(
|
978
|
-
min_word_length: min_word_length,
|
979
|
-
extra_dictionary_words: extra_dictionary_words) >= min_entropy
|
978
|
+
def is_strong?(password)
|
979
|
+
adjusted_entropy(password) >= min_entropy
|
980
980
|
end
|
981
981
|
|
982
|
-
def is_weak?(
|
983
|
-
!is_strong?(
|
982
|
+
def is_weak?(password)
|
983
|
+
!is_strong?(password)
|
984
984
|
end
|
985
985
|
|
986
986
|
# Returns the minimum entropy for the passwords dictionary adjustments.
|
@@ -988,13 +988,17 @@ module StrongPassword
|
|
988
988
|
# processing.
|
989
989
|
# Note that we only check for the first matching word up to the threshhold if set.
|
990
990
|
# Subsequent matching words are not deductd.
|
991
|
-
def adjusted_entropy(
|
992
|
-
|
991
|
+
def adjusted_entropy(password)
|
992
|
+
base_password = password.downcase
|
993
993
|
min_entropy = EntropyCalculator.calculate(base_password)
|
994
994
|
# Process the passwords, while looking for possible matching words in the dictionary.
|
995
995
|
PasswordVariants.all_variants(base_password).inject( min_entropy ) do |min_entropy, variant|
|
996
996
|
[ min_entropy, EntropyCalculator.calculate( variant.sub( dictionary_words, '*' ) ) ].min
|
997
997
|
end
|
998
998
|
end
|
999
|
+
|
1000
|
+
def dictionary_words
|
1001
|
+
@dictionary_words ||= Regexp.union( ( extra_dictionary_words + COMMON_PASSWORDS ).compact.reject{ |i| i.length < min_word_length } )
|
1002
|
+
end
|
999
1003
|
end
|
1000
1004
|
end
|
@@ -17,57 +17,61 @@ module StrongPassword
|
|
17
17
|
"abcdefghijklmnopqrstuvwxyz"
|
18
18
|
]
|
19
19
|
|
20
|
-
attr_reader :
|
20
|
+
attr_reader :min_entropy, :entropy_threshhold
|
21
21
|
|
22
|
-
def initialize(
|
23
|
-
@
|
22
|
+
def initialize(min_entropy: 18, entropy_threshhold: 0)
|
23
|
+
@min_entropy = min_entropy
|
24
|
+
@entropy_threshhold = entropy_threshhold
|
24
25
|
end
|
25
26
|
|
26
|
-
def is_strong?(
|
27
|
-
adjusted_entropy(
|
27
|
+
def is_strong?(base_password)
|
28
|
+
adjusted_entropy(base_password) >= min_entropy
|
28
29
|
end
|
29
30
|
|
30
|
-
def is_weak?(
|
31
|
-
!is_strong?(
|
31
|
+
def is_weak?(base_password)
|
32
|
+
!is_strong?(base_password)
|
32
33
|
end
|
33
34
|
|
34
35
|
# Returns the minimum entropy for the password's qwerty locality
|
35
36
|
# adjustments. If a threshhold is specified we will bail
|
36
37
|
# early to avoid unnecessary processing.
|
37
|
-
def adjusted_entropy(
|
38
|
+
def adjusted_entropy(base_password)
|
38
39
|
revpassword = base_password.reverse
|
39
40
|
min_entropy = [EntropyCalculator.calculate(base_password), EntropyCalculator.calculate(revpassword)].min
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
return min_entropy if min_entropy < entropy_threshhold
|
52
|
-
end
|
41
|
+
qpassword = mask_qwerty_strings(base_password)
|
42
|
+
qrevpassword = mask_qwerty_strings(revpassword)
|
43
|
+
if qpassword != base_password
|
44
|
+
numbits = EntropyCalculator.calculate(qpassword)
|
45
|
+
min_entropy = [min_entropy, numbits].min
|
46
|
+
return min_entropy if min_entropy < entropy_threshhold
|
47
|
+
end
|
48
|
+
if qrevpassword != revpassword
|
49
|
+
numbits = EntropyCalculator.calculate(qrevpassword)
|
50
|
+
min_entropy = [min_entropy, numbits].min
|
51
|
+
return min_entropy if min_entropy < entropy_threshhold
|
53
52
|
end
|
54
53
|
min_entropy
|
55
54
|
end
|
56
55
|
|
57
56
|
private
|
58
57
|
|
59
|
-
def
|
60
|
-
|
61
|
-
|
62
|
-
|
58
|
+
def all_qwerty_strings
|
59
|
+
@all_qwerty_strings ||= Regexp.union(QWERTY_STRINGS.flat_map do |qwerty_string|
|
60
|
+
gen_qw_strings(qwerty_string)
|
61
|
+
end)
|
62
|
+
end
|
63
|
+
|
64
|
+
def gen_qw_strings(qwerty_string)
|
65
|
+
6.downto(3).flat_map do |z|
|
63
66
|
y = qwerty_string.length - z
|
64
|
-
(0..y).
|
65
|
-
|
66
|
-
masked_password = masked_password.sub(str, '*')
|
67
|
+
(0..y).map do |x|
|
68
|
+
qwerty_string[x, z].sub('-', '\\-')
|
67
69
|
end
|
68
|
-
|
69
|
-
|
70
|
-
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def mask_qwerty_strings(password)
|
74
|
+
password.gsub(all_qwerty_strings, '*')
|
71
75
|
end
|
72
76
|
end
|
73
77
|
end
|
@@ -4,37 +4,55 @@ module StrongPassword
|
|
4
4
|
PASSWORD_LIMIT = 1_000
|
5
5
|
EXTRA_WORDS_LIMIT = 1_000
|
6
6
|
|
7
|
-
attr_reader :
|
7
|
+
attr_reader :min_entropy, :use_dictionary, :min_word_length, :extra_dictionary_words
|
8
8
|
|
9
|
-
def initialize(
|
10
|
-
@
|
9
|
+
def initialize(min_entropy: BASE_ENTROPY, use_dictionary: false, min_word_length: 4, extra_dictionary_words: [])
|
10
|
+
@min_entropy = min_entropy
|
11
|
+
@use_dictionary = use_dictionary
|
12
|
+
@min_word_length = min_word_length
|
13
|
+
@extra_dictionary_words = extra_dictionary_words
|
11
14
|
end
|
12
15
|
|
13
|
-
def is_weak?(
|
14
|
-
!is_strong?(
|
15
|
-
use_dictionary: use_dictionary,
|
16
|
-
min_word_length: min_word_length,
|
17
|
-
extra_dictionary_words: extra_dictionary_words)
|
16
|
+
def is_weak?(password)
|
17
|
+
!is_strong?(password)
|
18
18
|
end
|
19
19
|
|
20
|
-
def is_strong?(
|
20
|
+
def is_strong?(password)
|
21
|
+
base_password = password.dup[0...PASSWORD_LIMIT]
|
21
22
|
weak = (EntropyCalculator.calculate(base_password) < min_entropy) ||
|
22
23
|
(EntropyCalculator.calculate(base_password.downcase) < min_entropy) ||
|
23
|
-
(
|
24
|
+
(qwerty_adjuster.is_weak?(base_password))
|
24
25
|
if !weak && use_dictionary
|
25
|
-
return
|
26
|
-
min_word_length: min_word_length,
|
27
|
-
extra_dictionary_words: extra_dictionary_words)
|
26
|
+
return dictionary_adjuster.is_strong?(base_password)
|
28
27
|
else
|
29
28
|
return !weak
|
30
29
|
end
|
31
30
|
end
|
32
31
|
|
33
|
-
def calculate_entropy(
|
32
|
+
def calculate_entropy(password)
|
33
|
+
base_password = password.dup[0...PASSWORD_LIMIT]
|
34
34
|
extra_dictionary_words.collect! { |w| w[0...EXTRA_WORDS_LIMIT] }
|
35
|
-
entropies = [
|
36
|
-
|
35
|
+
entropies = [
|
36
|
+
EntropyCalculator.calculate(base_password),
|
37
|
+
EntropyCalculator.calculate(base_password.downcase),
|
38
|
+
qwerty_adjuster.adjusted_entropy(base_password)
|
39
|
+
]
|
40
|
+
entropies << dictionary_adjuster.adjusted_entropy(base_password) if use_dictionary
|
37
41
|
entropies.min
|
38
42
|
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
def qwerty_adjuster
|
47
|
+
@qwerty_adjuster ||= QwertyAdjuster.new(min_entropy: min_entropy)
|
48
|
+
end
|
49
|
+
|
50
|
+
def dictionary_adjuster
|
51
|
+
@dictionary_adjuster ||= DictionaryAdjuster.new(
|
52
|
+
min_word_length: min_word_length,
|
53
|
+
extra_dictionary_words: extra_dictionary_words,
|
54
|
+
min_entropy: min_entropy
|
55
|
+
)
|
56
|
+
end
|
39
57
|
end
|
40
58
|
end
|
@@ -3,36 +3,36 @@ require 'spec_helper'
|
|
3
3
|
module StrongPassword
|
4
4
|
describe DictionaryAdjuster do
|
5
5
|
describe '#is_strong?' do
|
6
|
-
let(:subject) { DictionaryAdjuster.new
|
6
|
+
let(:subject) { DictionaryAdjuster.new }
|
7
7
|
|
8
8
|
it 'returns true if the calculated entropy is >= the minimum' do
|
9
|
-
subject.
|
10
|
-
expect(subject.is_strong?).to be_truthy
|
9
|
+
allow(subject).to receive_messages(adjusted_entropy: 18)
|
10
|
+
expect(subject.is_strong?('password')).to be_truthy
|
11
11
|
end
|
12
12
|
|
13
13
|
it 'returns false if the calculated entropy is < the minimum' do
|
14
|
-
subject.
|
15
|
-
expect(subject.is_strong?).to be_falsey
|
14
|
+
allow(subject).to receive_messages(adjusted_entropy: 17)
|
15
|
+
expect(subject.is_strong?('password')).to be_falsey
|
16
16
|
end
|
17
17
|
end
|
18
18
|
|
19
19
|
describe '#is_weak?' do
|
20
|
-
let(:subject) { DictionaryAdjuster.new
|
20
|
+
let(:subject) { DictionaryAdjuster.new }
|
21
21
|
|
22
22
|
it 'returns the opposite of is_strong?' do
|
23
|
-
subject.
|
24
|
-
expect(subject.is_weak?).to be_falsey
|
23
|
+
allow(subject).to receive_messages(is_strong?: true)
|
24
|
+
expect(subject.is_weak?('password')).to be_falsey
|
25
25
|
end
|
26
26
|
end
|
27
27
|
|
28
28
|
describe '#adjusted_entropy' do
|
29
|
-
before(:each) { NistBonusBits.
|
29
|
+
before(:each) { allow(NistBonusBits).to receive_messages(bonus_bits: 0) }
|
30
30
|
|
31
31
|
it 'checks against all variants of a given password' do
|
32
32
|
password = 'password'
|
33
|
-
adjuster = DictionaryAdjuster.new
|
34
|
-
PasswordVariants.
|
35
|
-
adjuster.adjusted_entropy
|
33
|
+
adjuster = DictionaryAdjuster.new
|
34
|
+
expect(PasswordVariants).to receive(:all_variants).with(password).and_return([])
|
35
|
+
adjuster.adjusted_entropy(password)
|
36
36
|
end
|
37
37
|
|
38
38
|
{
|
@@ -47,21 +47,21 @@ module StrongPassword
|
|
47
47
|
'asdf[]asdf' => 16 # Doesn't break with []s
|
48
48
|
}.each do |password, bits|
|
49
49
|
it "returns #{bits} for '#{password}'" do
|
50
|
-
expect(DictionaryAdjuster.new(password)
|
50
|
+
expect(DictionaryAdjuster.new.adjusted_entropy(password)).to eq(bits)
|
51
51
|
end
|
52
52
|
end
|
53
53
|
|
54
54
|
it 'allows extra words to be provided as an array' do
|
55
55
|
password = 'administratorWEQ@123'
|
56
56
|
base_entropy = EntropyCalculator.calculate(password)
|
57
|
-
expect(DictionaryAdjuster.new(
|
57
|
+
expect(DictionaryAdjuster.new(extra_dictionary_words: ['administrator']).adjusted_entropy(password)).not_to eq(base_entropy)
|
58
58
|
end
|
59
59
|
|
60
60
|
it 'allows minimum word length to be adjusted' do
|
61
61
|
password = '6969'
|
62
|
-
base_entropy = DictionaryAdjuster.new(password)
|
62
|
+
base_entropy = DictionaryAdjuster.new.adjusted_entropy(password)
|
63
63
|
# If we increase the min_word_length above the length of the password we should get a higher entropy
|
64
|
-
expect(DictionaryAdjuster.new(
|
64
|
+
expect(DictionaryAdjuster.new(min_word_length: 6).adjusted_entropy(password)).not_to be < base_entropy
|
65
65
|
end
|
66
66
|
end
|
67
67
|
end
|
@@ -5,15 +5,15 @@ module StrongPassword
|
|
5
5
|
describe '.bonus_bits' do
|
6
6
|
it 'calculates the bonus bits the first time for a given password' do
|
7
7
|
NistBonusBits.reset_bonus_cache!
|
8
|
-
NistBonusBits.
|
8
|
+
expect(NistBonusBits).to receive(:calculate_bonus_bits_for).and_return(1)
|
9
9
|
expect(NistBonusBits.bonus_bits('password')).to eq(1)
|
10
10
|
end
|
11
11
|
|
12
12
|
it 'caches the bonus bits for a password for later use' do
|
13
13
|
NistBonusBits.reset_bonus_cache!
|
14
|
-
NistBonusBits.
|
14
|
+
allow(NistBonusBits).to receive_messages(calculate_bonus_bits_for: 1)
|
15
15
|
NistBonusBits.bonus_bits('password')
|
16
|
-
NistBonusBits.
|
16
|
+
expect(NistBonusBits).not_to receive(:calculate_bonus_bits_for)
|
17
17
|
expect(NistBonusBits.bonus_bits('password')).to eq(1)
|
18
18
|
end
|
19
19
|
end
|
@@ -8,12 +8,12 @@ module StrongPassword
|
|
8
8
|
end
|
9
9
|
|
10
10
|
it 'includes keyboard shift variants' do
|
11
|
-
subject.
|
11
|
+
allow(subject).to receive_messages(keyboard_shift_variants: ['foo', 'bar'])
|
12
12
|
expect(subject.all_variants("password")).to include('foo', 'bar')
|
13
13
|
end
|
14
14
|
|
15
15
|
it 'includes leet speak variants' do
|
16
|
-
subject.
|
16
|
+
allow(subject).to receive_messages(leet_speak_variants: ['foo', 'bar'])
|
17
17
|
expect(subject.all_variants("password")).to include('foo', 'bar')
|
18
18
|
end
|
19
19
|
|
@@ -3,30 +3,30 @@ require 'spec_helper'
|
|
3
3
|
module StrongPassword
|
4
4
|
describe QwertyAdjuster do
|
5
5
|
describe '#is_strong?' do
|
6
|
-
let(:subject) { QwertyAdjuster.new
|
6
|
+
let(:subject) { QwertyAdjuster.new }
|
7
7
|
|
8
8
|
it 'returns true if the calculated entropy is >= the minimum' do
|
9
|
-
subject.
|
10
|
-
expect(subject.is_strong?).to be_truthy
|
9
|
+
allow(subject).to receive_messages(adjusted_entropy: 18)
|
10
|
+
expect(subject.is_strong?("password")).to be_truthy
|
11
11
|
end
|
12
12
|
|
13
13
|
it 'returns false if the calculated entropy is < the minimum' do
|
14
|
-
subject.
|
15
|
-
expect(subject.is_strong?).to be_falsey
|
14
|
+
allow(subject).to receive_messages(adjusted_entropy: 17)
|
15
|
+
expect(subject.is_strong?("password")).to be_falsey
|
16
16
|
end
|
17
17
|
end
|
18
18
|
|
19
19
|
describe '#is_weak?' do
|
20
|
-
let(:subject) { QwertyAdjuster.new
|
20
|
+
let(:subject) { QwertyAdjuster.new }
|
21
21
|
|
22
22
|
it 'returns the opposite of is_strong?' do
|
23
|
-
subject.
|
24
|
-
expect(subject.is_weak?).to be_falsey
|
23
|
+
allow(subject).to receive_messages(is_strong?: true)
|
24
|
+
expect(subject.is_weak?("password")).to be_falsey
|
25
25
|
end
|
26
26
|
end
|
27
27
|
|
28
28
|
describe '#adjusted_entropy' do
|
29
|
-
before(:each) { NistBonusBits.
|
29
|
+
before(:each) { allow(NistBonusBits).to receive_messages(bonus_bits: 0)}
|
30
30
|
{
|
31
31
|
'qwertyuio' => 5.5,
|
32
32
|
'1234567' => 6,
|
@@ -37,7 +37,7 @@ module StrongPassword
|
|
37
37
|
'password' => 17.5 # Ensure that we don't qwerty-adjust 'password'
|
38
38
|
}.each do |password, bits|
|
39
39
|
it "returns #{bits} for '#{password}'" do
|
40
|
-
expect(QwertyAdjuster.new(password)
|
40
|
+
expect(QwertyAdjuster.new.adjusted_entropy(password)).to eq(bits)
|
41
41
|
end
|
42
42
|
end
|
43
43
|
end
|
@@ -11,7 +11,7 @@ module StrongPassword
|
|
11
11
|
'aB$1' => false
|
12
12
|
}.each do |password, strength|
|
13
13
|
it "is_strong? returns #{strength} for '#{password}' with 12 bits of entropy" do
|
14
|
-
expect(StrengthChecker.new(
|
14
|
+
expect(StrengthChecker.new(min_entropy: 12).is_strong?(password)).to be(strength)
|
15
15
|
end
|
16
16
|
end
|
17
17
|
end
|
@@ -26,7 +26,7 @@ module StrongPassword
|
|
26
26
|
'aB$1' => false
|
27
27
|
}.each do |password, strength|
|
28
28
|
it "is_strong? returns #{strength} for '#{password}' with 12 bits of entropy" do
|
29
|
-
expect(StrengthChecker.new(
|
29
|
+
expect(StrengthChecker.new(min_entropy: 12, use_dictionary: true).is_strong?(password)).to eq(strength)
|
30
30
|
end
|
31
31
|
end
|
32
32
|
end
|
@@ -41,7 +41,7 @@ module StrongPassword
|
|
41
41
|
'correct horse battery staple' => true
|
42
42
|
}.each do |password, strength|
|
43
43
|
it "is_strong? returns #{strength} for '#{password}' with standard bits of entropy" do
|
44
|
-
expect(StrengthChecker.new(
|
44
|
+
expect(StrengthChecker.new(use_dictionary: true).is_strong?(password)).to eq(strength)
|
45
45
|
end
|
46
46
|
end
|
47
47
|
end
|
@@ -57,28 +57,31 @@ module StrongPassword
|
|
57
57
|
'c0rr#ct h0rs3 Batt$ry st@pl3 is Gr34t' => true
|
58
58
|
}.each do |password, strength|
|
59
59
|
it "is_strong? returns #{strength} for '#{password}' with standard bits of entropy" do
|
60
|
-
expect(StrengthChecker.new(
|
60
|
+
expect(StrengthChecker.new(min_entropy: 40, use_dictionary: true).is_strong?(password)).to eq(strength)
|
61
61
|
end
|
62
62
|
end
|
63
63
|
end
|
64
64
|
|
65
65
|
context 'with long password' do
|
66
|
-
let(:strength_checker) { StrengthChecker.new
|
66
|
+
let(:strength_checker) { StrengthChecker.new }
|
67
|
+
let(:password) { ("ba"*500_000) }
|
67
68
|
it 'should be truncated' do
|
68
|
-
expect(strength_checker.
|
69
|
+
expect(strength_checker.calculate_entropy(password)).
|
70
|
+
to eq(strength_checker.calculate_entropy(password.slice(0, StrengthChecker::PASSWORD_LIMIT)))
|
69
71
|
end
|
70
72
|
end
|
71
73
|
|
72
74
|
context 'with long extra words' do
|
73
|
-
let(:strength_checker) { StrengthChecker.new("
|
75
|
+
let(:strength_checker) { StrengthChecker.new(use_dictionary: true, extra_dictionary_words: ["a"*1_000_000, "b"*10_000_000, "c"*10]) }
|
74
76
|
let(:exta_limit) { StrengthChecker::EXTRA_WORDS_LIMIT }
|
75
77
|
it 'should be truncated' do
|
76
|
-
|
78
|
+
expect(DictionaryAdjuster).to receive(:new).with({
|
79
|
+
min_entropy: 18,
|
77
80
|
min_word_length: 4,
|
78
81
|
extra_dictionary_words:
|
79
82
|
["a"*StrengthChecker::EXTRA_WORDS_LIMIT, "b"*StrengthChecker::EXTRA_WORDS_LIMIT, "c"*10]
|
80
83
|
}).and_call_original
|
81
|
-
strength_checker.calculate_entropy(
|
84
|
+
strength_checker.calculate_entropy("$tr0NgP4s$w0rd91d£")
|
82
85
|
end
|
83
86
|
end
|
84
87
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: strong_password
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.8
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Brian McManus
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2019-07-08 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|