strong_password 0.0.1

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.
@@ -0,0 +1,153 @@
1
+ module StrongPassword
2
+ module PasswordVariants
3
+ LEET_SPEAK_1 = {
4
+ "@" => "a",
5
+ "!" => "i",
6
+ "$" => "s",
7
+ "1" => "i",
8
+ "2" => "z",
9
+ "3" => "e",
10
+ "4" => "a",
11
+ "5" => "s",
12
+ "6" => "g",
13
+ "7" => "t",
14
+ "8" => "b",
15
+ "9" => "g",
16
+ "0" => "o"
17
+ }
18
+
19
+ LEET_SPEAK_2 = {
20
+ "@" => "a",
21
+ "!" => "i",
22
+ "$" => "s",
23
+ "1" => "l",
24
+ "2" => "z",
25
+ "3" => "e",
26
+ "4" => "a",
27
+ "5" => "s",
28
+ "6" => "g",
29
+ "7" => "t",
30
+ "8" => "b",
31
+ "9" => "g",
32
+ "0" => "o"
33
+ }
34
+
35
+ KEYBOARDMAP_DOWN_NOSHIFT = {
36
+ "z" => "",
37
+ "x" => "",
38
+ "c" => "",
39
+ "v" => "",
40
+ "b" => "",
41
+ "n" => "",
42
+ "m" => "",
43
+ "," => "",
44
+ "." => "",
45
+ "/" => "",
46
+ "<" => "",
47
+ ">" => "",
48
+ "?" => ""
49
+ }
50
+
51
+ KEYBOARDMAP_DOWNRIGHT = {
52
+ "a" => "z",
53
+ "q" => "a",
54
+ "1" => "q",
55
+ "s" => "x",
56
+ "w" => "s",
57
+ "2" => "w",
58
+ "d" => "c",
59
+ "e" => "d",
60
+ "3" => "e",
61
+ "f" => "v",
62
+ "r" => "f",
63
+ "4" => "r",
64
+ "g" => "b",
65
+ "t" => "g",
66
+ "5" => "t",
67
+ "h" => "n",
68
+ "y" => "h",
69
+ "6" => "y",
70
+ "j" => "m",
71
+ "u" => "j",
72
+ "7" => "u",
73
+ "i" => "k",
74
+ "8" => "i",
75
+ "o" => "l",
76
+ "9" => "o",
77
+ "0" => "p"
78
+ }
79
+
80
+ KEYBOARDMAP_DOWNLEFT = {
81
+ "2" => "q",
82
+ "w" => "a",
83
+ "3" => "w",
84
+ "s" => "z",
85
+ "e" => "s",
86
+ "4" => "e",
87
+ "d" => "x",
88
+ "r" => "d",
89
+ "5" => "r",
90
+ "f" => "c",
91
+ "t" => "f",
92
+ "6" => "t",
93
+ "g" => "v",
94
+ "y" => "g",
95
+ "7" => "y",
96
+ "h" => "b",
97
+ "u" => "h",
98
+ "8" => "u",
99
+ "j" => "n",
100
+ "i" => "j",
101
+ "9" => "i",
102
+ "k" => "m",
103
+ "o" => "k",
104
+ "0" => "o",
105
+ "p" => "l",
106
+ "-" => "p"
107
+ }
108
+
109
+ # Returns all variants of a given password including the password itself
110
+ def self.all_variants(password)
111
+ passwords = [password.dup.downcase]
112
+ passwords += keyboard_shift_variants(password)
113
+ passwords += leet_speak_variants(password)
114
+ passwords.uniq
115
+ end
116
+
117
+ # Returns all keyboard shifted variants of a given password
118
+ def self.keyboard_shift_variants(password)
119
+ password = password.dup.downcase
120
+ variants = []
121
+
122
+ if (password == password.tr(KEYBOARDMAP_DOWN_NOSHIFT.keys.join, KEYBOARDMAP_DOWN_NOSHIFT.values.join))
123
+ variant = password.tr(KEYBOARDMAP_DOWNRIGHT.keys.join, KEYBOARDMAP_DOWNRIGHT.values.join)
124
+ variants << variant
125
+ variants << variant.reverse
126
+
127
+ variant = password.tr(KEYBOARDMAP_DOWNLEFT.keys.join, KEYBOARDMAP_DOWNLEFT.values.join)
128
+ variants << variant
129
+ variants << variant.reverse
130
+ end
131
+ variants
132
+ end
133
+
134
+ # Returns all leet speak variants of a given password
135
+ def self.leet_speak_variants(password)
136
+ password = password.dup.downcase
137
+ variants = []
138
+
139
+ leet = password.tr(LEET_SPEAK_1.keys.join, LEET_SPEAK_1.values.join)
140
+ if leet != password
141
+ variants << leet
142
+ variants << leet.reverse
143
+ end
144
+
145
+ leet_l = password.tr(LEET_SPEAK_2.keys.join, LEET_SPEAK_2.values.join)
146
+ if (leet_l != password && leet_l != leet)
147
+ variants << leet_l
148
+ variants << leet_l.reverse
149
+ end
150
+ variants
151
+ end
152
+ end
153
+ end
@@ -0,0 +1,73 @@
1
+ module StrongPassword
2
+ class QwertyAdjuster
3
+ QWERTY_STRINGS = [
4
+ "1234567890-",
5
+ "qwertyuiop",
6
+ "asdfghjkl;",
7
+ "zxcvbnm,./",
8
+ "1qaz2wsx3edc4rfv5tgb6yhn7ujm8ik,9ol.0p;/-['=]:?_{\"+}",
9
+ "1qaz2wsx3edc4rfv5tgb6yhn7ujm8ik9ol0p",
10
+ "qazwsxedcrfvtgbyhnujmik,ol.p;/-['=]:?_{\"+}",
11
+ "qazwsxedcrfvtgbyhnujmikolp",
12
+ "]\"/=[;.-pl,0okm9ijn8uhb7ygv6tfc5rdx4esz3wa2q1",
13
+ "pl0okm9ijn8uhb7ygv6tfc5rdx4esz3wa2q1",
14
+ "]\"/[;.pl,okmijnuhbygvtfcrdxeszwaq",
15
+ "plokmijnuhbygvtfcrdxeszwaq",
16
+ "014725836914702583697894561230258/369*+-*/",
17
+ "abcdefghijklmnopqrstuvwxyz"
18
+ ]
19
+
20
+ attr_reader :base_password
21
+
22
+ def initialize(password)
23
+ @base_password = password.dup.downcase
24
+ end
25
+
26
+ def is_strong?(min_entropy: 18)
27
+ adjusted_entropy(entropy_threshhold: min_entropy) >= min_entropy
28
+ end
29
+
30
+ def is_weak?(min_entropy: 18)
31
+ !is_strong?(min_entropy: min_entropy)
32
+ end
33
+
34
+ # Returns the minimum entropy for the password's qwerty locality
35
+ # adjustments. If a threshhold is specified we will bail
36
+ # early to avoid unnecessary processing.
37
+ def adjusted_entropy(entropy_threshhold: 0)
38
+ revpassword = base_password.reverse
39
+ min_entropy = [EntropyCalculator.calculate(base_password), EntropyCalculator.calculate(revpassword)].min
40
+ QWERTY_STRINGS.each do |qwertystr|
41
+ qpassword = mask_qwerty_strings(base_password, qwertystr)
42
+ qrevpassword = mask_qwerty_strings(revpassword, qwertystr)
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
52
+ end
53
+ end
54
+ min_entropy
55
+ end
56
+
57
+ private
58
+
59
+ def mask_qwerty_strings(password, qwerty_string)
60
+ masked_password = password
61
+ z = 6
62
+ begin
63
+ y = qwerty_string.length - z
64
+ (0..y).each do |x|
65
+ str = qwerty_string[x, z].sub('-', '\\-')
66
+ masked_password = masked_password.sub(str, '*')
67
+ end
68
+ z = z - 1
69
+ end while z > 2
70
+ masked_password
71
+ end
72
+ end
73
+ end
@@ -0,0 +1 @@
1
+ require 'rails/railtie'
@@ -0,0 +1,37 @@
1
+ module StrongPassword
2
+ class StrengthChecker
3
+ BASE_ENTROPY = 18
4
+
5
+ attr_reader :base_password
6
+
7
+ def initialize(password)
8
+ @base_password = password.dup
9
+ end
10
+
11
+ def is_weak?(min_entropy: BASE_ENTROPY, use_dictionary: false, min_word_length: 4, extra_dictionary_words: [])
12
+ !is_strong?(min_entropy: min_entropy,
13
+ use_dictionary: use_dictionary,
14
+ min_word_length: min_word_length,
15
+ extra_dictionary_words: extra_dictionary_words)
16
+ end
17
+
18
+ def is_strong?(min_entropy: BASE_ENTROPY, use_dictionary: false, min_word_length: 4, extra_dictionary_words: [])
19
+ weak = (EntropyCalculator.calculate(base_password) < min_entropy) ||
20
+ (EntropyCalculator.calculate(base_password.downcase) < min_entropy) ||
21
+ (QwertyAdjuster.new(base_password).is_weak?(min_entropy: min_entropy))
22
+ if !weak && use_dictionary
23
+ return DictionaryAdjuster.new(base_password).is_strong?(min_entropy: min_entropy,
24
+ min_word_length: min_word_length,
25
+ extra_dictionary_words: extra_dictionary_words)
26
+ else
27
+ return !weak
28
+ end
29
+ end
30
+
31
+ def calculate_entropy(use_dictionary: false, min_word_length: 4, extra_dictionary_words: [])
32
+ entropies = [EntropyCalculator.calculate(base_password), EntropyCalculator.calculate(base_password.downcase), QwertyAdjuster.new(base_password).adjusted_entropy]
33
+ entropies << DictionaryAdjuster.new(base_password).adjusted_entropy(min_word_length: min_word_length, extra_dictionary_words: extra_dictionary_words) if use_dictionary
34
+ entropies.min
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,3 @@
1
+ module StrongPassword
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,15 @@
1
+ require 'active_model/validations'
2
+
3
+ require 'strong_password/version'
4
+ require 'strong_password/nist_bonus_bits'
5
+ require 'strong_password/entropy_calculator'
6
+ require 'strong_password/strength_checker'
7
+ require 'strong_password/password_variants'
8
+ require 'strong_password/dictionary_adjuster'
9
+ require 'strong_password/qwerty_adjuster'
10
+ require 'active_model/validations/password_strength_validator' if defined?(ActiveModel)
11
+
12
+ module StrongPassword
13
+ end
14
+
15
+ I18n.load_path << File.dirname(__FILE__) + '/strong_password/locale/en.yml' if defined?(I18n)
@@ -0,0 +1,8 @@
1
+ require 'bundler/setup'
2
+ require 'strong_password'
3
+ require 'active_model'
4
+
5
+ RSpec.configure do |config|
6
+ config.expect_with(:rspec) {|c| c.syntax = :expect}
7
+ config.order = :random
8
+ end
@@ -0,0 +1,66 @@
1
+ require 'spec_helper'
2
+
3
+ module StrongPassword
4
+ describe DictionaryAdjuster do
5
+ describe '#is_strong?' do
6
+ let(:subject) { DictionaryAdjuster.new('password') }
7
+
8
+ it 'returns true if the calculated entropy is >= the minimum' do
9
+ subject.stub(adjusted_entropy: 18)
10
+ expect(subject.is_strong?).to be_true
11
+ end
12
+
13
+ it 'returns false if the calculated entropy is < the minimum' do
14
+ subject.stub(adjusted_entropy: 17)
15
+ expect(subject.is_strong?).to be_false
16
+ end
17
+ end
18
+
19
+ describe '#is_weak?' do
20
+ let(:subject) { DictionaryAdjuster.new('password') }
21
+
22
+ it 'returns the opposite of is_strong?' do
23
+ subject.stub(is_strong?: true)
24
+ expect(subject.is_weak?).to be_false
25
+ end
26
+ end
27
+
28
+ describe '#adjusted_entropy' do
29
+ before(:each) { NistBonusBits.stub(bonus_bits: 0)}
30
+
31
+ it 'checks against all variants of a given password' do
32
+ password = 'password'
33
+ adjuster = DictionaryAdjuster.new(password)
34
+ PasswordVariants.should_receive(:all_variants).with(password).and_return([])
35
+ adjuster.adjusted_entropy
36
+ end
37
+
38
+ {
39
+ 'bnm,./' => 14, # Qwerty string should not get adjusted by dictionary adjuster
40
+ 'h#e0zbPas' => 19.5, # Random string should not get adjusted by dictionary adjuster
41
+ 'password' => 4, # Adjusts common dictionary words
42
+ 'E_!3password' => 11.5, # Adjusts common dictionary words regardless of placement
43
+ 'h#e0zbPas 32e2i81 password' => 31.3125, # Even if there are multiple words
44
+ '123456' => 4, # Even if they are also qwerty strings
45
+ 'password123456' => 16 # But only drops the first matched word
46
+ }.each do |password, bits|
47
+ it "returns #{bits} for '#{password}'" do
48
+ expect(DictionaryAdjuster.new(password).adjusted_entropy).to eq(bits)
49
+ end
50
+ end
51
+
52
+ it 'allows extra words to be provided as an array' do
53
+ password = 'mcmanus'
54
+ base_entropy = EntropyCalculator.calculate(password)
55
+ expect(DictionaryAdjuster.new(password).adjusted_entropy(extra_dictionary_words: ['mcmanus'])).not_to eq(base_entropy)
56
+ end
57
+
58
+ it 'allows minimum word length to be adjusted' do
59
+ password = '6969'
60
+ base_entropy = DictionaryAdjuster.new(password).adjusted_entropy
61
+ # If we increase the min_word_length above the length of the password we should get a higher entropy
62
+ expect(DictionaryAdjuster.new(password).adjusted_entropy(min_word_length: 6)).not_to be < base_entropy
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,65 @@
1
+ require 'spec_helper'
2
+
3
+ module StrongPassword
4
+ describe EntropyCalculator do
5
+ describe '.bits' do
6
+ before(:each) { NistBonusBits.stub(bonus_bits: 0) }
7
+ {
8
+ '' => 0,
9
+ '*' => 4,
10
+ '**' => 6,
11
+ '***' => 8,
12
+ '****' => 10,
13
+ '*****' => 12,
14
+ '******' => 14,
15
+ '*******' => 16,
16
+ '********' => 18,
17
+ '*********' => 19.5,
18
+ '**********' => 21,
19
+ '***********' => 22.5,
20
+ '************' => 24,
21
+ '*************' => 25.5,
22
+ '**************' => 27,
23
+ '***************' => 28.5,
24
+ '****************' => 30,
25
+ '*****************' => 31.5,
26
+ '******************' => 33,
27
+ '*******************' => 34.5,
28
+ '********************' => 36,
29
+ '*********************' => 37,
30
+ '**********************' => 38,
31
+ '***********************' => 39,
32
+ '************************' => 40
33
+ }.each do |password, bits|
34
+ it "returns #{bits} for #{password.length} characters" do
35
+ expect(subject.bits(password)).to eq(bits)
36
+ end
37
+ end
38
+ end
39
+
40
+ describe '.bits_with_repeats_weakened' do
41
+ before(:each) { NistBonusBits.stub(bonus_bits: 0) }
42
+ {
43
+ '' => 0,
44
+ '*' => 4,
45
+ '**' => 5.5,
46
+ '***' => 6.625,
47
+ '****' => 7.46875,
48
+ '****a' => 9.46875,
49
+ '********' => 9.1990966796875
50
+ }.each do |password, bits|
51
+ it "returns #{bits} for #{password.length} characters" do
52
+ expect(subject.bits_with_repeats_weakened(password)).to eq(bits)
53
+ end
54
+ end
55
+
56
+ it 'returns the same value for repeated calls on a password' do
57
+ password = 'password'
58
+ initial_value = subject.bits_with_repeats_weakened(password)
59
+ 5.times do
60
+ expect(subject.bits_with_repeats_weakened(password)).to eq(initial_value)
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,45 @@
1
+ require 'spec_helper'
2
+
3
+ module StrongPassword
4
+ describe NistBonusBits do
5
+ describe '.bonus_bits' do
6
+ it 'calculates the bonus bits the first time for a given password' do
7
+ NistBonusBits.reset_bonus_cache!
8
+ NistBonusBits.should_receive(:calculate_bonus_bits_for).and_return(1)
9
+ expect(NistBonusBits.bonus_bits('password')).to eq(1)
10
+ end
11
+
12
+ it 'caches the bonus bits for a password for later use' do
13
+ NistBonusBits.reset_bonus_cache!
14
+ NistBonusBits.stub(calculate_bonus_bits_for: 1)
15
+ NistBonusBits.bonus_bits('password')
16
+ NistBonusBits.should_not_receive(:calculate_bonus_bits_for)
17
+ expect(NistBonusBits.bonus_bits('password')).to eq(1)
18
+ end
19
+ end
20
+
21
+ describe '.calculate_bonus_bits_for' do
22
+ {
23
+ 'Ab$9' => 4,
24
+ 'Ab $9' => 6,
25
+ 'A b $ 9' => 7,
26
+ 'Ab$' => 3,
27
+ 'Ab $' => 5,
28
+ 'A b $ c d' => 6,
29
+ '1!' => -4,
30
+ '1 !' => -2,
31
+ '1 ! 2 # 3 $' => -1,
32
+ '123' => -8,
33
+ '1 23' => -6,
34
+ '1 2 3 4 5 6' => -5,
35
+ 'blah' => -2,
36
+ 'blah blah' => 0,
37
+ 'blah blah blah blah' => 1
38
+ }.each do |password, bonus_bits|
39
+ it "returns #{bonus_bits} for '#{password}'" do
40
+ expect(NistBonusBits.calculate_bonus_bits_for(password)).to eq(bonus_bits)
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,67 @@
1
+ require 'spec_helper'
2
+
3
+ module StrongPassword
4
+ describe PasswordVariants do
5
+ describe '.all_variants' do
6
+ it 'includes the lowercase password' do
7
+ expect(subject.all_variants("PASSWORD")).to include('password')
8
+ end
9
+
10
+ it 'includes keyboard shift variants' do
11
+ subject.stub(keyboard_shift_variants: ['foo', 'bar'])
12
+ expect(subject.all_variants("password")).to include('foo', 'bar')
13
+ end
14
+
15
+ it 'includes leet speak variants' do
16
+ subject.stub(leet_speak_variants: ['foo', 'bar'])
17
+ expect(subject.all_variants("password")).to include('foo', 'bar')
18
+ end
19
+
20
+ it 'does not mutate the password' do
21
+ password = 'PASSWORD'
22
+ subject.all_variants(password)
23
+ expect(password).to eq('PASSWORD')
24
+ end
25
+ end
26
+
27
+ describe '.keyboard_shift_variants' do
28
+ it 'returns no variants if password includes only bottom row characters' do
29
+ expect(subject.keyboard_shift_variants('zxcvbnm,./')).to eq([])
30
+ end
31
+
32
+ it 'maps down-right passwords' do
33
+ expect(subject.keyboard_shift_variants('qwerty')).to include('asdfgh')
34
+ end
35
+
36
+ it 'includes reversed down-right password' do
37
+ expect(subject.keyboard_shift_variants('qwerty')).to include('hgfdsa')
38
+ end
39
+
40
+ it 'maps down-left passwords' do
41
+ expect(subject.keyboard_shift_variants('sdfghj')).to include('zxcvbn')
42
+ end
43
+
44
+ it 'maps reversed down-left passwords' do
45
+ expect(subject.keyboard_shift_variants('sdfghj')).to include('nbvcxz')
46
+ end
47
+ end
48
+
49
+ describe '.leet_speak_variants' do
50
+ it 'returns no variants if the password includes no leet speak' do
51
+ expect(subject.leet_speak_variants('password')).to eq([])
52
+ end
53
+
54
+ it 'returns standard leet speak variants' do
55
+ expect(subject.leet_speak_variants('p4ssw0rd')).to include('password')
56
+ end
57
+
58
+ it 'returns reversed standard leet speak variants' do
59
+ expect(subject.leet_speak_variants('p4ssw0rd')).to include('drowssap')
60
+ end
61
+
62
+ it 'returns both i and l variants when given a 1' do
63
+ expect(subject.leet_speak_variants('h1b0b')).to include('hibob', 'hlbob')
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,45 @@
1
+ require 'spec_helper'
2
+
3
+ module StrongPassword
4
+ describe QwertyAdjuster do
5
+ describe '#is_strong?' do
6
+ let(:subject) { QwertyAdjuster.new('password') }
7
+
8
+ it 'returns true if the calculated entropy is >= the minimum' do
9
+ subject.stub(adjusted_entropy: 18)
10
+ expect(subject.is_strong?).to be_true
11
+ end
12
+
13
+ it 'returns false if the calculated entropy is < the minimum' do
14
+ subject.stub(adjusted_entropy: 17)
15
+ expect(subject.is_strong?).to be_false
16
+ end
17
+ end
18
+
19
+ describe '#is_weak?' do
20
+ let(:subject) { QwertyAdjuster.new('password') }
21
+
22
+ it 'returns the opposite of is_strong?' do
23
+ subject.stub(is_strong?: true)
24
+ expect(subject.is_weak?).to be_false
25
+ end
26
+ end
27
+
28
+ describe '#adjusted_entropy' do
29
+ before(:each) { NistBonusBits.stub(bonus_bits: 0)}
30
+ {
31
+ 'qwertyuio' => 5.5,
32
+ '1234567' => 6,
33
+ 'lkjhgfd' => 6,
34
+ '0987654321' => 5.5,
35
+ 'zxcvbnm' => 6,
36
+ '/.,mnbvcx' => 5.5,
37
+ 'password' => 17.5 # Ensure that we don't qwerty-adjust 'password'
38
+ }.each do |password, bits|
39
+ it "returns #{bits} for '#{password}'" do
40
+ expect(QwertyAdjuster.new(password).adjusted_entropy).to eq(bits)
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,64 @@
1
+ require 'spec_helper'
2
+
3
+ module StrongPassword
4
+ describe StrengthChecker do
5
+ context 'with lowered entropy requirement and no dictionary checking' do
6
+ {
7
+ 'blahblah' => true,
8
+ 'password' => true,
9
+ 'wwwwwwww' => false,
10
+ 'adamruge' => true,
11
+ 'aB$1' => false
12
+ }.each do |password, strength|
13
+ it "is_strong? returns #{strength} for '#{password}' with 12 bits of entropy" do
14
+ expect(StrengthChecker.new(password).is_strong?(min_entropy: 12)).to be(strength)
15
+ end
16
+ end
17
+ end
18
+
19
+ context 'with lowered entropy requirement and dictionary checking' do
20
+ {
21
+ 'blahblah' => true,
22
+ 'password' => false,
23
+ 'wwwwwwww' => false,
24
+ 'adamruge' => true,
25
+ 'aB$1' => false
26
+ }.each do |password, strength|
27
+ it "is_strong? returns #{strength} for '#{password}' with 12 bits of entropy" do
28
+ expect(StrengthChecker.new(password).is_strong?(min_entropy: 12, use_dictionary: true)).to be(strength)
29
+ end
30
+ end
31
+ end
32
+
33
+ context 'with standard entropy requirement and dictionary checking' do
34
+ {
35
+ 'blahblah' => false,
36
+ 'password' => false,
37
+ 'wwwwwwww' => false,
38
+ 'adamruge' => false,
39
+ 'aB$1' => false,
40
+ 'correct horse battery staple' => true
41
+ }.each do |password, strength|
42
+ it "is_strong? returns #{strength} for '#{password}' with standard bits of entropy" do
43
+ expect(StrengthChecker.new(password).is_strong?(use_dictionary: true)).to be(strength)
44
+ end
45
+ end
46
+ end
47
+
48
+ context 'with crazy entropy requirement and dictionary checking' do
49
+ {
50
+ 'blahblah' => false,
51
+ 'password' => false,
52
+ 'wwwwwwww' => false,
53
+ 'adamruge' => false,
54
+ 'aB$1' => false,
55
+ 'correct horse battery staple' => false,
56
+ 'c0rr#ct h0rs3 Batt$ry st@pl3 is Gr34t' => true
57
+ }.each do |password, strength|
58
+ it "is_strong? returns #{strength} for '#{password}' with standard bits of entropy" do
59
+ expect(StrengthChecker.new(password).is_strong?(min_entropy: 40, use_dictionary: true)).to be(strength)
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end