strong_password 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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