pwfoo 0.1.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.
- data/Manifest +17 -0
- data/README.rdoc +3 -0
- data/Rakefile +14 -0
- data/features/pwfoo_calculate_password_strength.feature +21 -0
- data/features/pwfoo_generate_random_password.feature +13 -0
- data/features/step_definitions/password_steps.rb +29 -0
- data/features/support/env.rb +4 -0
- data/lib/pwfoo.rb +4 -0
- data/lib/pwfoo/generate_password.rb +48 -0
- data/lib/pwfoo/password_score_calculator.rb +180 -0
- data/lib/pwfoo/password_strength.rb +16 -0
- data/lib/pwfoo/srand_seed_generator.rb +20 -0
- data/pwfoo.gemspec +30 -0
- data/spec/pwfoo/generates_password_spec.rb +88 -0
- data/spec/pwfoo/password_score_calculator_spec.rb +221 -0
- data/spec/pwfoo/random_seed_generator_spec.rb +19 -0
- data/spec/spec_helper.rb +3 -0
- metadata +81 -0
data/Manifest
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
Manifest
|
2
|
+
README.rdoc
|
3
|
+
Rakefile
|
4
|
+
features/pwfoo_calculate_password_strength.feature
|
5
|
+
features/pwfoo_generate_random_password.feature
|
6
|
+
features/step_definitions/password_steps.rb
|
7
|
+
features/support/env.rb
|
8
|
+
lib/pwfoo.rb
|
9
|
+
lib/pwfoo/generate_password.rb
|
10
|
+
lib/pwfoo/password_score_calculator.rb
|
11
|
+
lib/pwfoo/password_strength.rb
|
12
|
+
lib/pwfoo/srand_seed_generator.rb
|
13
|
+
pwfoo.gemspec
|
14
|
+
spec/pwfoo/generates_password_spec.rb
|
15
|
+
spec/pwfoo/password_score_calculator_spec.rb
|
16
|
+
spec/pwfoo/random_seed_generator_spec.rb
|
17
|
+
spec/spec_helper.rb
|
data/README.rdoc
ADDED
data/Rakefile
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
require 'echoe'
|
4
|
+
|
5
|
+
Echoe.new("pwfoo", "0.1.0") do |p|
|
6
|
+
p.author = "Perry Hertler"
|
7
|
+
p.summary = "A gem that generates strong random passwords, scores the strength of passwords, and generates random seeds."
|
8
|
+
p.url = "http://github.com/perry3819/pwfoo"
|
9
|
+
p.email = "perry@hertler.org"
|
10
|
+
p.ignore_pattern = ["tmp/*", "script/*, .idea/*"]
|
11
|
+
p.development_dependencies = []
|
12
|
+
end
|
13
|
+
|
14
|
+
Dir["#File.dirname(__FILE__)}/tasks/*.rake"].sort.each { |ext| load ext }
|
@@ -0,0 +1,21 @@
|
|
1
|
+
Feature: password manager submit password
|
2
|
+
The password manager submits a password to receive
|
3
|
+
a password strength score.
|
4
|
+
|
5
|
+
Scenario Outline: submit password
|
6
|
+
Given the password is <password>
|
7
|
+
When I submit
|
8
|
+
Then the score should be <score>
|
9
|
+
|
10
|
+
Scenarios: valid passwords
|
11
|
+
| password | score |
|
12
|
+
| ate*92. | 56 |
|
13
|
+
| a | 3 |
|
14
|
+
| | 0 |
|
15
|
+
| ate*92.U1^ | 100 |
|
16
|
+
| 123456789 | 4 |
|
17
|
+
| 0*f#8G(3i | 100 |
|
18
|
+
| 0*432tuvUi | 86 |
|
19
|
+
| 0432tuvi | 44 |
|
20
|
+
| 0432*$ | 51 |
|
21
|
+
|
@@ -0,0 +1,13 @@
|
|
1
|
+
Feature: password manager requests a random password
|
2
|
+
The password manager clicks generate_password to receive
|
3
|
+
a randomly generated strong password.
|
4
|
+
|
5
|
+
Scenario Outline: request password
|
6
|
+
Given the requested length is <length>
|
7
|
+
When I request
|
8
|
+
Then the generated length will be <gen_length>
|
9
|
+
|
10
|
+
Scenarios: valid lengths
|
11
|
+
| length | gen_length |
|
12
|
+
| 1 | 1 |
|
13
|
+
| 100 | 100 |
|
@@ -0,0 +1,29 @@
|
|
1
|
+
def password_strength
|
2
|
+
@password_strength ||= PwFoo::PasswordStrength.new
|
3
|
+
end
|
4
|
+
|
5
|
+
Given /^the password is (.*)/ do |pw|
|
6
|
+
@score = password_strength.calculate_score pw
|
7
|
+
end
|
8
|
+
|
9
|
+
When /^I submit$/ do
|
10
|
+
|
11
|
+
end
|
12
|
+
|
13
|
+
Then /^the score should be (.*)/ do |expected_score|
|
14
|
+
@score.should == expected_score.to_i
|
15
|
+
end
|
16
|
+
|
17
|
+
Given /^the requested length is (.*)/ do |length|
|
18
|
+
@gen_password = PwFoo::GeneratePassword.new(length.to_i).generate
|
19
|
+
end
|
20
|
+
|
21
|
+
When /^I request$/ do
|
22
|
+
|
23
|
+
end
|
24
|
+
|
25
|
+
Then /^the generated length will be (.*)/ do | expected_length |
|
26
|
+
expected_length.to_i.should == @gen_password.length
|
27
|
+
end
|
28
|
+
|
29
|
+
|
data/lib/pwfoo.rb
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
module PwFoo
|
2
|
+
class GeneratePassword
|
3
|
+
LOWER_CASE = 'lower_case'
|
4
|
+
UPPER_CASE = 'upper_case'
|
5
|
+
NUMBERS = 'numbers'
|
6
|
+
SPECIALS = 'specials'
|
7
|
+
|
8
|
+
@@character_pool = {
|
9
|
+
LOWER_CASE => ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'],
|
10
|
+
UPPER_CASE => ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'],
|
11
|
+
NUMBERS => ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'],
|
12
|
+
SPECIALS => ['!', '@', '#', '$', '%', '^', '*', '_', '-', '*', '(', ')']
|
13
|
+
}
|
14
|
+
|
15
|
+
def initialize(length = 10, *allowed_types)
|
16
|
+
@length = length
|
17
|
+
@allowed_types = allowed_types == nil || 0 == allowed_types.length ? [LOWER_CASE, UPPER_CASE, NUMBERS] : allowed_types
|
18
|
+
@pw_strength_finder = PasswordStrength.new
|
19
|
+
srand(SrandSeedGenerator.new().get_next_seed)
|
20
|
+
end
|
21
|
+
|
22
|
+
def generate_with_min_strength(min_strength)
|
23
|
+
gen = nil
|
24
|
+
while gen == nil || min_strength > @pw_strength_finder.calculate_score(gen) do
|
25
|
+
gen = generate
|
26
|
+
end
|
27
|
+
gen
|
28
|
+
end
|
29
|
+
|
30
|
+
def generate
|
31
|
+
gen_pw = ''
|
32
|
+
@length.times { |i|
|
33
|
+
gen_pw.concat next_char
|
34
|
+
}
|
35
|
+
gen_pw
|
36
|
+
end
|
37
|
+
|
38
|
+
def next_char
|
39
|
+
ta = next_type_array
|
40
|
+
ta[rand(ta.length)]
|
41
|
+
end
|
42
|
+
|
43
|
+
def next_type_array
|
44
|
+
@@character_pool[@allowed_types[rand(@allowed_types.length)]]
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,180 @@
|
|
1
|
+
module PwFoo
|
2
|
+
class PasswordScoreCalculator
|
3
|
+
SYMBOLS_REGEX = /[^a-zA-Z0-9_]/
|
4
|
+
LETTERS_REGEX = /[A-Za-z]/
|
5
|
+
UPPER_CASE_REGEX = /[A-Z]/
|
6
|
+
LOWER_CASE_REGEX = /[a-z]/
|
7
|
+
NUMBERS_REGEX = /\d/
|
8
|
+
|
9
|
+
def initialize(word)
|
10
|
+
if word == nil then
|
11
|
+
@word = ''
|
12
|
+
else
|
13
|
+
@word = word
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def number_of_characters_score
|
18
|
+
4 * @word.length
|
19
|
+
end
|
20
|
+
|
21
|
+
def upper_case_letters_score
|
22
|
+
case_letters_score(UPPER_CASE_REGEX)
|
23
|
+
end
|
24
|
+
|
25
|
+
def symbols_score
|
26
|
+
zero_if_empty{6 * count(SYMBOLS_REGEX)}
|
27
|
+
end
|
28
|
+
|
29
|
+
def numbers_score
|
30
|
+
return zero_if_empty{
|
31
|
+
count = count(NUMBERS_REGEX)
|
32
|
+
count < @word.length ? 4 * count : 0
|
33
|
+
}
|
34
|
+
end
|
35
|
+
|
36
|
+
def repeat_characters_deduction
|
37
|
+
repeats = _consecutive_count {|last, current| current == last}
|
38
|
+
- (repeats * (repeats - 1))
|
39
|
+
end
|
40
|
+
|
41
|
+
def consecutive_numbers_deduction
|
42
|
+
regex = NUMBERS_REGEX
|
43
|
+
repeats = _consecutive_count {|last, current| nil != current && nil != last && current.chr =~ regex && last.chr =~ regex}
|
44
|
+
return - (repeats * 2)
|
45
|
+
end
|
46
|
+
|
47
|
+
def consecutive_upper_case_letters_deduction
|
48
|
+
_consecutive_case_letters_deduction(UPPER_CASE_REGEX)
|
49
|
+
end
|
50
|
+
|
51
|
+
def consecutive_lower_case_letters_deduction
|
52
|
+
_consecutive_case_letters_deduction(LOWER_CASE_REGEX)
|
53
|
+
end
|
54
|
+
|
55
|
+
def _consecutive_case_letters_deduction(regex)
|
56
|
+
repeats = _consecutive_count {|last, current| nil != current && nil != last && current.chr =~ regex && last.chr =~ regex}
|
57
|
+
- (repeats * 2)
|
58
|
+
end
|
59
|
+
|
60
|
+
def sequential_letters_deduction
|
61
|
+
repeats = _three_consecutive_count(lambda {|first, second, third|
|
62
|
+
consec = false
|
63
|
+
if nil != first && first.chr =~ LETTERS_REGEX && nil != second && second.chr =~ LETTERS_REGEX && nil != third && third.chr =~ LETTERS_REGEX then
|
64
|
+
consec = _are_consecutive first.chr.downcase[0], second.chr.downcase[0], third.chr.downcase[0]
|
65
|
+
end
|
66
|
+
return consec
|
67
|
+
})
|
68
|
+
- (repeats * 3)
|
69
|
+
end
|
70
|
+
|
71
|
+
def sequential_numbers_deduction
|
72
|
+
repeats = _three_consecutive_count(lambda {|first, second, third|
|
73
|
+
consec = false
|
74
|
+
if nil != first && first.chr =~ NUMBERS_REGEX && nil != second && second.chr =~ NUMBERS_REGEX && nil != third && first.chr =~ NUMBERS_REGEX then
|
75
|
+
consec = _are_consecutive first, second, third
|
76
|
+
end
|
77
|
+
consec
|
78
|
+
})
|
79
|
+
- (repeats * 3)
|
80
|
+
end
|
81
|
+
|
82
|
+
def _are_consecutive(first, second, third)
|
83
|
+
(second == (first + 1) && third == (second + 1)) || (second == (first -1) && third == (second -1))
|
84
|
+
end
|
85
|
+
|
86
|
+
def _three_consecutive_count(is_consecutive)
|
87
|
+
repeats = 0
|
88
|
+
first = nil, second = nil, third = nil
|
89
|
+
|
90
|
+
0.upto(@word.length - 1) { |i|
|
91
|
+
first = second
|
92
|
+
second = third
|
93
|
+
third = @word[i]
|
94
|
+
if is_consecutive.call(first, second, third) then
|
95
|
+
repeats = repeats + 1
|
96
|
+
end
|
97
|
+
}
|
98
|
+
|
99
|
+
repeats
|
100
|
+
end
|
101
|
+
|
102
|
+
def _consecutive_count(&is_consecutive)
|
103
|
+
repeats = 0
|
104
|
+
|
105
|
+
cur_char = nil
|
106
|
+
0.upto(@word.length - 1) { |i|
|
107
|
+
if is_consecutive.call(cur_char, @word[i]) then
|
108
|
+
repeats = repeats + 1
|
109
|
+
end
|
110
|
+
cur_char = @word[i]
|
111
|
+
}
|
112
|
+
|
113
|
+
repeats
|
114
|
+
end
|
115
|
+
|
116
|
+
def letters_only_deduction
|
117
|
+
only_deduction(LETTERS_REGEX)
|
118
|
+
end
|
119
|
+
|
120
|
+
def numbers_only_deduction
|
121
|
+
only_deduction(NUMBERS_REGEX)
|
122
|
+
end
|
123
|
+
|
124
|
+
def only_deduction(regex)
|
125
|
+
zero_if_empty{
|
126
|
+
@word.length == count(regex) ? (- @word.length) : 0
|
127
|
+
}
|
128
|
+
end
|
129
|
+
|
130
|
+
def lower_case_letters_score
|
131
|
+
case_letters_score(/[a-z]/)
|
132
|
+
end
|
133
|
+
|
134
|
+
def case_letters_score(regex)
|
135
|
+
zero_if_empty {
|
136
|
+
count = count(regex)
|
137
|
+
count > 0 ? ((@word.length - count(regex)) * 2) : 0
|
138
|
+
}
|
139
|
+
end
|
140
|
+
|
141
|
+
def zero_if_empty(&calculator)
|
142
|
+
0 == number_of_characters_score ? 0 : calculator.call
|
143
|
+
end
|
144
|
+
|
145
|
+
def middle_numbers_or_symbols_score
|
146
|
+
middle = @word[1, @word.length-2]
|
147
|
+
@word.length < 3 ? 0 : (2 * (count(SYMBOLS_REGEX, middle) + count(NUMBERS_REGEX, middle)))
|
148
|
+
end
|
149
|
+
|
150
|
+
def count(regex, count_word=@word)
|
151
|
+
count_word.scan(regex).length
|
152
|
+
end
|
153
|
+
|
154
|
+
def requirements_score
|
155
|
+
0
|
156
|
+
end
|
157
|
+
|
158
|
+
def print_all
|
159
|
+
puts '=============================================================='
|
160
|
+
puts 'Score for word: ' << @word
|
161
|
+
puts 'Number of Characters: ' << number_of_characters_score.to_s
|
162
|
+
puts 'Uppercase Letters: ' << upper_case_letters_score.to_s
|
163
|
+
puts 'Lowercase Letters: ' << lower_case_letters_score.to_s
|
164
|
+
puts 'Numbers: ' << numbers_score.to_s
|
165
|
+
puts 'Symbols: ' << symbols_score.to_s
|
166
|
+
puts 'Middle Numbers or Symbols: ' << middle_numbers_or_symbols_score.to_s
|
167
|
+
puts 'Requirements: ' << requirements_score.to_s
|
168
|
+
puts 'Letters Only: ' << letters_only_deduction.to_s
|
169
|
+
puts 'Numbers Only: ' << numbers_only_deduction.to_s
|
170
|
+
puts 'Repeat Characters: ' << repeat_characters_deduction.to_s
|
171
|
+
puts 'Consecutive Uppercase Letters: ' << consecutive_upper_case_letters_deduction.to_s
|
172
|
+
puts 'Consecutive Lowercase Letters: ' << consecutive_lower_case_letters_deduction.to_s
|
173
|
+
puts 'Consecutive Numbers: ' << consecutive_numbers_deduction.to_s
|
174
|
+
puts 'Sequential Letters: ' << sequential_letters_deduction.to_s
|
175
|
+
puts 'Sequential Numbers: ' << sequential_numbers_deduction.to_s
|
176
|
+
puts '=============================================================='
|
177
|
+
end
|
178
|
+
|
179
|
+
end
|
180
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module PwFoo
|
2
|
+
class PasswordStrength
|
3
|
+
def initialize
|
4
|
+
end
|
5
|
+
|
6
|
+
def calculate_score(pw)
|
7
|
+
sc = PasswordScoreCalculator.new(pw)
|
8
|
+
total = sc.number_of_characters_score + sc.upper_case_letters_score + sc.lower_case_letters_score + sc.numbers_score + sc.symbols_score + sc.middle_numbers_or_symbols_score + sc.requirements_score + sc.letters_only_deduction + sc.numbers_only_deduction + sc.repeat_characters_deduction + sc.consecutive_upper_case_letters_deduction + sc.consecutive_lower_case_letters_deduction + sc.consecutive_numbers_deduction + sc.sequential_letters_deduction + sc.sequential_numbers_deduction
|
9
|
+
total > 100 ? 100 : total
|
10
|
+
end
|
11
|
+
|
12
|
+
def print_score(pw)
|
13
|
+
PasswordScoreCalculator.new(pw).print_all
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module PwFoo
|
2
|
+
require 'digest/md5'
|
3
|
+
|
4
|
+
class SrandSeedGenerator
|
5
|
+
def initialize
|
6
|
+
end
|
7
|
+
|
8
|
+
def get_next_seed
|
9
|
+
time_in_micro = Time.new().to_f * 100000
|
10
|
+
checksum = _get_checksum
|
11
|
+
|
12
|
+
time_in_micro * time_in_micro + checksum.to_i
|
13
|
+
end
|
14
|
+
|
15
|
+
def _get_checksum
|
16
|
+
Digest::MD5.hexdigest(`ps axww | gzip`)
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
end
|
data/pwfoo.gemspec
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
Gem::Specification.new do |s|
|
4
|
+
s.name = %q{pwfoo}
|
5
|
+
s.version = "0.1.0"
|
6
|
+
|
7
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 1.2") if s.respond_to? :required_rubygems_version=
|
8
|
+
s.authors = ["Perry Hertler"]
|
9
|
+
s.date = %q{2009-11-10}
|
10
|
+
s.description = %q{A gem that generates strong random passwords, scores the strength of passwords, and generates random seeds.}
|
11
|
+
s.email = %q{perry@hertler.org}
|
12
|
+
s.extra_rdoc_files = ["README.rdoc", "lib/pwfoo.rb", "lib/pwfoo/generate_password.rb", "lib/pwfoo/password_score_calculator.rb", "lib/pwfoo/password_strength.rb", "lib/pwfoo/srand_seed_generator.rb"]
|
13
|
+
s.files = ["Manifest", "README.rdoc", "Rakefile", "features/pwfoo_calculate_password_strength.feature", "features/pwfoo_generate_random_password.feature", "features/step_definitions/password_steps.rb", "features/support/env.rb", "lib/pwfoo.rb", "lib/pwfoo/generate_password.rb", "lib/pwfoo/password_score_calculator.rb", "lib/pwfoo/password_strength.rb", "lib/pwfoo/srand_seed_generator.rb", "pwfoo.gemspec", "spec/pwfoo/generates_password_spec.rb", "spec/pwfoo/password_score_calculator_spec.rb", "spec/pwfoo/random_seed_generator_spec.rb", "spec/spec_helper.rb"]
|
14
|
+
s.homepage = %q{http://github.com/perry3819/pwfoo}
|
15
|
+
s.rdoc_options = ["--line-numbers", "--inline-source", "--title", "Pwfoo", "--main", "README.rdoc"]
|
16
|
+
s.require_paths = ["lib"]
|
17
|
+
s.rubyforge_project = %q{pwfoo}
|
18
|
+
s.rubygems_version = %q{1.3.5}
|
19
|
+
s.summary = %q{A gem that generates strong random passwords, scores the strength of passwords, and generates random seeds.}
|
20
|
+
|
21
|
+
if s.respond_to? :specification_version then
|
22
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
23
|
+
s.specification_version = 3
|
24
|
+
|
25
|
+
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
26
|
+
else
|
27
|
+
end
|
28
|
+
else
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), "..", "spec_helper")
|
2
|
+
|
3
|
+
module PwFoo
|
4
|
+
describe GeneratePassword do
|
5
|
+
include GeneratePasswordHelper
|
6
|
+
|
7
|
+
before(:all) do
|
8
|
+
Spec::Matchers.define :be_greater_than_or_equal do |min_score|
|
9
|
+
match do |score|
|
10
|
+
score >= min_score
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
before(:each) do
|
16
|
+
|
17
|
+
end
|
18
|
+
|
19
|
+
context "with defaults" do
|
20
|
+
it "should have length of 7" do
|
21
|
+
PwFoo::GeneratePassword.new(7).generate.length.should == 7
|
22
|
+
end
|
23
|
+
it "should have length of 0" do
|
24
|
+
PwFoo::GeneratePassword.new(0).generate.length.should == 0
|
25
|
+
end
|
26
|
+
it "should have length of 1" do
|
27
|
+
PwFoo::GeneratePassword.new(1).generate.length.should == 1
|
28
|
+
end
|
29
|
+
it "should have length of 100" do
|
30
|
+
PwFoo::GeneratePassword.new(100).generate.length.should == 100
|
31
|
+
end
|
32
|
+
context "with just letters" do
|
33
|
+
def length_of_nonletters (word)
|
34
|
+
word.scan(/[^A-Za-z]/).length
|
35
|
+
end
|
36
|
+
|
37
|
+
it "should contain no numbers or specials" do
|
38
|
+
gw = PwFoo::GeneratePassword.new(12, PwFoo::GeneratePassword::LOWER_CASE, PwFoo::GeneratePassword::UPPER_CASE)
|
39
|
+
length_of_nonletters(gw.generate).should == 0
|
40
|
+
length_of_nonletters(gw.generate).should == 0
|
41
|
+
length_of_nonletters(gw.generate).should == 0
|
42
|
+
length_of_nonletters(gw.generate).should == 0
|
43
|
+
end
|
44
|
+
end
|
45
|
+
context "with just numbers" do
|
46
|
+
def length_of_nonnumbers(word)
|
47
|
+
word.scan(/[^0-9]/).length
|
48
|
+
end
|
49
|
+
|
50
|
+
it "should contain no letters or specials" do
|
51
|
+
gw = PwFoo::GeneratePassword.new(12, PwFoo::GeneratePassword::NUMBERS)
|
52
|
+
length_of_nonnumbers(gw.generate).should == 0
|
53
|
+
length_of_nonnumbers(gw.generate).should == 0
|
54
|
+
length_of_nonnumbers(gw.generate).should == 0
|
55
|
+
length_of_nonnumbers(gw.generate).should == 0
|
56
|
+
end
|
57
|
+
end
|
58
|
+
context "with just specials" do
|
59
|
+
def length_of_nonspecials(word)
|
60
|
+
word.scan(/[A-Za-z0-9]/).length
|
61
|
+
end
|
62
|
+
|
63
|
+
it "should contain no letters or specials" do
|
64
|
+
gw = PwFoo::GeneratePassword.new(12, PwFoo::GeneratePassword::SPECIALS)
|
65
|
+
length_of_nonspecials(gw.generate).should == 0
|
66
|
+
length_of_nonspecials(gw.generate).should == 0
|
67
|
+
length_of_nonspecials(gw.generate).should == 0
|
68
|
+
length_of_nonspecials(gw.generate).should == 0
|
69
|
+
end
|
70
|
+
end
|
71
|
+
context "with minimum strength" do
|
72
|
+
def generate_with_min_strength(min_s)
|
73
|
+
gp = PwFoo::GeneratePassword.new(12, PwFoo::GeneratePassword::SPECIALS, PwFoo::GeneratePassword::LOWER_CASE, PwFoo::GeneratePassword::UPPER_CASE, PwFoo::GeneratePassword::NUMBERS)
|
74
|
+
gp.generate_with_min_strength min_s
|
75
|
+
end
|
76
|
+
|
77
|
+
def calc_strength(word)
|
78
|
+
PasswordStrength.new.calculate_score(word)
|
79
|
+
end
|
80
|
+
|
81
|
+
it "should have a strength greater than 70" do
|
82
|
+
pw = generate_with_min_strength(70)
|
83
|
+
calc_strength(pw).should be_greater_than_or_equal( 70 )
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
@@ -0,0 +1,221 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), "..", "spec_helper")
|
2
|
+
|
3
|
+
module PwFoo
|
4
|
+
describe PasswordScoreCalculator do
|
5
|
+
before(:each) do
|
6
|
+
@score_calculator = PwFoo::PasswordScoreCalculator.new('a4bV%(z')
|
7
|
+
@nil_score_calculator = PwFoo::PasswordScoreCalculator.new(nil)
|
8
|
+
end
|
9
|
+
context "with upper case letters" do
|
10
|
+
it "should return 12" do
|
11
|
+
@score_calculator.upper_case_letters_score.should == 12
|
12
|
+
end
|
13
|
+
end
|
14
|
+
context "with nil password" do
|
15
|
+
it "should return 0" do
|
16
|
+
@nil_score_calculator.upper_case_letters_score.should == 0
|
17
|
+
@nil_score_calculator.lower_case_letters_score.should == 0
|
18
|
+
@nil_score_calculator.symbols_score.should == 0
|
19
|
+
@nil_score_calculator.middle_numbers_or_symbols_score.should == 0
|
20
|
+
@nil_score_calculator.repeat_characters_deduction.should == 0
|
21
|
+
@nil_score_calculator.consecutive_upper_case_letters_deduction.should == 0
|
22
|
+
@nil_score_calculator.consecutive_lower_case_letters_deduction.should == 0
|
23
|
+
@nil_score_calculator.consecutive_numbers_deduction.should == 0
|
24
|
+
@nil_score_calculator.sequential_letters_deduction.should == 0
|
25
|
+
@nil_score_calculator.sequential_numbers_deduction.should == 0
|
26
|
+
@nil_score_calculator.numbers_only_deduction.should == 0
|
27
|
+
@nil_score_calculator.letters_only_deduction.should == 0
|
28
|
+
@nil_score_calculator.numbers_score.should == 0
|
29
|
+
@nil_score_calculator.number_of_characters_score.should == 0
|
30
|
+
end
|
31
|
+
end
|
32
|
+
context "with no upper case letters" do
|
33
|
+
it "should return 0" do
|
34
|
+
PwFoo::PasswordScoreCalculator.new('ate*92.').upper_case_letters_score.should == 0
|
35
|
+
end
|
36
|
+
end
|
37
|
+
context "with lower case letters" do
|
38
|
+
it "should return 8" do
|
39
|
+
@score_calculator.lower_case_letters_score.should == 8
|
40
|
+
end
|
41
|
+
end
|
42
|
+
context "with no lower case letters" do
|
43
|
+
it "should return 0" do
|
44
|
+
PwFoo::PasswordScoreCalculator.new('AA').lower_case_letters_score.should == 0
|
45
|
+
end
|
46
|
+
end
|
47
|
+
context "with symbols" do
|
48
|
+
it "should return 12" do
|
49
|
+
@score_calculator.symbols_score.should == 12
|
50
|
+
end
|
51
|
+
end
|
52
|
+
context "with no symbols" do
|
53
|
+
it "should return 0" do
|
54
|
+
PwFoo::PasswordScoreCalculator.new('AAa').symbols_score.should == 0
|
55
|
+
end
|
56
|
+
end
|
57
|
+
context "with middle numbers or symbols" do
|
58
|
+
it "should return 6" do
|
59
|
+
@score_calculator.middle_numbers_or_symbols_score.should == 6
|
60
|
+
end
|
61
|
+
end
|
62
|
+
context "with no middle numbers or symbols" do
|
63
|
+
it "should return 0" do
|
64
|
+
PwFoo::PasswordScoreCalculator.new('AAa').middle_numbers_or_symbols_score.should == 0
|
65
|
+
end
|
66
|
+
end
|
67
|
+
context "with no requirements score" do
|
68
|
+
it "should return 0" do
|
69
|
+
@score_calculator.requirements_score.should == 0
|
70
|
+
end
|
71
|
+
end
|
72
|
+
context "with repeat characters" do
|
73
|
+
it "should return -2" do
|
74
|
+
PwFoo::PasswordScoreCalculator.new('8a77bbw').repeat_characters_deduction.should == -2
|
75
|
+
end
|
76
|
+
it "should return -6" do
|
77
|
+
PwFoo::PasswordScoreCalculator.new('8a777bbw').repeat_characters_deduction.should == -6
|
78
|
+
end
|
79
|
+
end
|
80
|
+
context "with no repeat characters" do
|
81
|
+
it "should return 0" do
|
82
|
+
@score_calculator.repeat_characters_deduction.should == 0
|
83
|
+
end
|
84
|
+
end
|
85
|
+
context "with consecutive upper case" do
|
86
|
+
it "should return -2" do
|
87
|
+
PwFoo::PasswordScoreCalculator.new('8a77BBw').consecutive_upper_case_letters_deduction.should == -2
|
88
|
+
end
|
89
|
+
it "should return -8" do
|
90
|
+
PwFoo::PasswordScoreCalculator.new('8aBB777CCCCbbw').consecutive_upper_case_letters_deduction.should == -8
|
91
|
+
end
|
92
|
+
end
|
93
|
+
context "with no consecutive upper case" do
|
94
|
+
it "should return 0" do
|
95
|
+
@score_calculator.consecutive_upper_case_letters_deduction.should == 0
|
96
|
+
end
|
97
|
+
end
|
98
|
+
context "with consecutive lower case" do
|
99
|
+
it "should return -2" do
|
100
|
+
PwFoo::PasswordScoreCalculator.new('8a77zzW').consecutive_lower_case_letters_deduction.should == -2
|
101
|
+
end
|
102
|
+
it "should return -14" do
|
103
|
+
PwFoo::PasswordScoreCalculator.new('8abe777dddbbw').consecutive_lower_case_letters_deduction.should == -14
|
104
|
+
end
|
105
|
+
end
|
106
|
+
context "with no consecutive lower case" do
|
107
|
+
it "should return 0" do
|
108
|
+
@score_calculator.consecutive_lower_case_letters_deduction.should == 0
|
109
|
+
end
|
110
|
+
end
|
111
|
+
context "with consecutive numbers" do
|
112
|
+
it "should return -2" do
|
113
|
+
PwFoo::PasswordScoreCalculator.new('8a77zzW').consecutive_numbers_deduction.should == -2
|
114
|
+
end
|
115
|
+
it "should return -10" do
|
116
|
+
PwFoo::PasswordScoreCalculator.new('8abe777dddbbw8361').consecutive_numbers_deduction.should == -10
|
117
|
+
end
|
118
|
+
it "should return -16" do
|
119
|
+
PwFoo::PasswordScoreCalculator.new('123456789').consecutive_numbers_deduction.should == -16
|
120
|
+
end
|
121
|
+
end
|
122
|
+
context "with no consecutive numbers" do
|
123
|
+
it "should return 0" do
|
124
|
+
@score_calculator.consecutive_numbers_deduction.should == 0
|
125
|
+
end
|
126
|
+
it "should return 0" do
|
127
|
+
PwFoo::PasswordScoreCalculator.new('0*f#8G(3i').consecutive_numbers_deduction.should == 0
|
128
|
+
end
|
129
|
+
end
|
130
|
+
context "with sequential letters" do
|
131
|
+
it "should return -3" do
|
132
|
+
PwFoo::PasswordScoreCalculator.new('aBc872ai').sequential_letters_deduction.should == -3
|
133
|
+
end
|
134
|
+
it "should return -6" do
|
135
|
+
PwFoo::PasswordScoreCalculator.new('8abe777tSrQdddbbw').sequential_letters_deduction.should == -6
|
136
|
+
end
|
137
|
+
end
|
138
|
+
context "with no sequential letters" do
|
139
|
+
it "should return 0" do
|
140
|
+
@score_calculator.sequential_letters_deduction.should == 0
|
141
|
+
end
|
142
|
+
end
|
143
|
+
context "with sequential numbers" do
|
144
|
+
it "should return -3" do
|
145
|
+
PwFoo::PasswordScoreCalculator.new('aBc854372ai').sequential_numbers_deduction.should == -3
|
146
|
+
end
|
147
|
+
it "should return -3" do
|
148
|
+
PwFoo::PasswordScoreCalculator.new('8abe77789tSrQdddbbw').sequential_numbers_deduction.should == -3
|
149
|
+
end
|
150
|
+
it "should return -12" do
|
151
|
+
PwFoo::PasswordScoreCalculator.new('8abe77789tSr56789Qdddbbw').sequential_numbers_deduction.should == -12
|
152
|
+
end
|
153
|
+
it "should return -21" do
|
154
|
+
PwFoo::PasswordScoreCalculator.new('123456789').sequential_numbers_deduction.should == -21
|
155
|
+
end
|
156
|
+
end
|
157
|
+
context "with no sequential numbers" do
|
158
|
+
it "should return 0" do
|
159
|
+
@score_calculator.sequential_numbers_deduction.should == 0
|
160
|
+
end
|
161
|
+
it "should return 0" do
|
162
|
+
PwFoo::PasswordScoreCalculator.new('0*f#8G(3i').sequential_numbers_deduction.should == 0
|
163
|
+
end
|
164
|
+
end
|
165
|
+
context "with numbers only" do
|
166
|
+
it "should return -6" do
|
167
|
+
PwFoo::PasswordScoreCalculator.new('123456').numbers_only_deduction.should == -6
|
168
|
+
end
|
169
|
+
it "should return -9" do
|
170
|
+
PwFoo::PasswordScoreCalculator.new('123456789').numbers_only_deduction.should == -9
|
171
|
+
end
|
172
|
+
end
|
173
|
+
context "with no sequential numbers" do
|
174
|
+
it "should return 0" do
|
175
|
+
@score_calculator.numbers_only_deduction.should == 0
|
176
|
+
end
|
177
|
+
it "should return 0" do
|
178
|
+
PwFoo::PasswordScoreCalculator.new('0*f#8G(3i').numbers_only_deduction.should == 0
|
179
|
+
end
|
180
|
+
end
|
181
|
+
context "with letters only" do
|
182
|
+
it "should return -5" do
|
183
|
+
PwFoo::PasswordScoreCalculator.new('abcde').letters_only_deduction.should == -5
|
184
|
+
end
|
185
|
+
end
|
186
|
+
context "with no letters" do
|
187
|
+
it "should return 0" do
|
188
|
+
@score_calculator.letters_only_deduction.should == 0
|
189
|
+
end
|
190
|
+
end
|
191
|
+
context "with numbers score" do
|
192
|
+
it "should return 4" do
|
193
|
+
@score_calculator.numbers_score.should == 4
|
194
|
+
end
|
195
|
+
end
|
196
|
+
context "with no numbers score" do
|
197
|
+
it "should return 0" do
|
198
|
+
PwFoo::PasswordScoreCalculator.new('123456789').numbers_score.should == 0
|
199
|
+
end
|
200
|
+
end
|
201
|
+
context "with characters score" do
|
202
|
+
it "should return 28" do
|
203
|
+
@score_calculator.number_of_characters_score.should == 28
|
204
|
+
end
|
205
|
+
end
|
206
|
+
context "with count" do
|
207
|
+
it "should return 4" do
|
208
|
+
@score_calculator.count(/[A-Za-z]/).should == 4
|
209
|
+
end
|
210
|
+
it "should return 1" do
|
211
|
+
@score_calculator.count(/\d/).should == 1
|
212
|
+
end
|
213
|
+
it "should return 2" do
|
214
|
+
@score_calculator.count(/\W/).should == 2
|
215
|
+
end
|
216
|
+
it "should return 2" do
|
217
|
+
@score_calculator.count(/[^a-zA-Z0-9_]/).should == 2
|
218
|
+
end
|
219
|
+
end
|
220
|
+
end
|
221
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), "..", "spec_helper")
|
2
|
+
|
3
|
+
module PwFoo
|
4
|
+
describe SrandSeedGenerator do
|
5
|
+
before(:each) do
|
6
|
+
@seed_gen = SrandSeedGenerator.new
|
7
|
+
end
|
8
|
+
context "with different seeds" do
|
9
|
+
it "should not be equal" do
|
10
|
+
@seed_gen.get_next_seed.should_not == @seed_gen.get_next_seed
|
11
|
+
end
|
12
|
+
end
|
13
|
+
context "with checksum" do
|
14
|
+
it "should not be nil" do
|
15
|
+
@seed_gen._get_checksum.should_not be_nil
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,81 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: pwfoo
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Perry Hertler
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-11-10 00:00:00 -06:00
|
13
|
+
default_executable:
|
14
|
+
dependencies: []
|
15
|
+
|
16
|
+
description: A gem that generates strong random passwords, scores the strength of passwords, and generates random seeds.
|
17
|
+
email: perry@hertler.org
|
18
|
+
executables: []
|
19
|
+
|
20
|
+
extensions: []
|
21
|
+
|
22
|
+
extra_rdoc_files:
|
23
|
+
- README.rdoc
|
24
|
+
- lib/pwfoo.rb
|
25
|
+
- lib/pwfoo/generate_password.rb
|
26
|
+
- lib/pwfoo/password_score_calculator.rb
|
27
|
+
- lib/pwfoo/password_strength.rb
|
28
|
+
- lib/pwfoo/srand_seed_generator.rb
|
29
|
+
files:
|
30
|
+
- Manifest
|
31
|
+
- README.rdoc
|
32
|
+
- Rakefile
|
33
|
+
- features/pwfoo_calculate_password_strength.feature
|
34
|
+
- features/pwfoo_generate_random_password.feature
|
35
|
+
- features/step_definitions/password_steps.rb
|
36
|
+
- features/support/env.rb
|
37
|
+
- lib/pwfoo.rb
|
38
|
+
- lib/pwfoo/generate_password.rb
|
39
|
+
- lib/pwfoo/password_score_calculator.rb
|
40
|
+
- lib/pwfoo/password_strength.rb
|
41
|
+
- lib/pwfoo/srand_seed_generator.rb
|
42
|
+
- pwfoo.gemspec
|
43
|
+
- spec/pwfoo/generates_password_spec.rb
|
44
|
+
- spec/pwfoo/password_score_calculator_spec.rb
|
45
|
+
- spec/pwfoo/random_seed_generator_spec.rb
|
46
|
+
- spec/spec_helper.rb
|
47
|
+
has_rdoc: true
|
48
|
+
homepage: http://github.com/perry3819/pwfoo
|
49
|
+
licenses: []
|
50
|
+
|
51
|
+
post_install_message:
|
52
|
+
rdoc_options:
|
53
|
+
- --line-numbers
|
54
|
+
- --inline-source
|
55
|
+
- --title
|
56
|
+
- Pwfoo
|
57
|
+
- --main
|
58
|
+
- README.rdoc
|
59
|
+
require_paths:
|
60
|
+
- lib
|
61
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
62
|
+
requirements:
|
63
|
+
- - ">="
|
64
|
+
- !ruby/object:Gem::Version
|
65
|
+
version: "0"
|
66
|
+
version:
|
67
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
68
|
+
requirements:
|
69
|
+
- - ">="
|
70
|
+
- !ruby/object:Gem::Version
|
71
|
+
version: "1.2"
|
72
|
+
version:
|
73
|
+
requirements: []
|
74
|
+
|
75
|
+
rubyforge_project: pwfoo
|
76
|
+
rubygems_version: 1.3.5
|
77
|
+
signing_key:
|
78
|
+
specification_version: 3
|
79
|
+
summary: A gem that generates strong random passwords, scores the strength of passwords, and generates random seeds.
|
80
|
+
test_files: []
|
81
|
+
|