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