is_passgen 1.1.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,283 @@
1
+ module Passgen
2
+ class StrengthAnalyzer
3
+ MIN_LENGTH = 8
4
+ attr_reader :password, :score, :complexity, :errors
5
+
6
+ def initialize(pw)
7
+ @password = pw
8
+ @score = 0
9
+ @complexity = "Invalid"
10
+ @errors = []
11
+ end
12
+
13
+ def self.analyze(pw)
14
+ sa = StrengthAnalyzer.new(pw)
15
+ sa.analyze
16
+ sa
17
+ end
18
+
19
+ def analyze
20
+ return self unless check_minimum_requirements
21
+
22
+ nScore = 0
23
+ nLength = 0
24
+ nAlphaUC = 0
25
+ nAlphaLC = 0
26
+ nNumber = 0
27
+ nSymbol = 0
28
+ nMidChar = 0
29
+ nRequirements = 0
30
+ nAlphasOnly = 0
31
+ nNumbersOnly = 0
32
+ nUnqChar = 0
33
+ nRepChar = 0
34
+ nRepInc = 0
35
+ nConsecAlphaUC = 0
36
+ nConsecAlphaLC = 0
37
+ nConsecNumber = 0
38
+ nConsecSymbol = 0
39
+ nConsecCharType = 0
40
+ nSeqAlpha = 0
41
+ nSeqNumber = 0
42
+ nSeqSymbol = 0
43
+ nSeqChar = 0
44
+ nReqChar = 0
45
+ nMultConsecCharType = 0
46
+ nMultRepChar = 1
47
+ nMultConsecSymbol = 1
48
+ nMultMidChar = 2
49
+ nMultRequirements = 2
50
+ nMultConsecAlphaUC = 2
51
+ nMultConsecAlphaLC = 2
52
+ nMultConsecNumber = 2
53
+ nReqCharType = 3
54
+ nMultAlphaUC = 3
55
+ nMultAlphaLC = 3
56
+ nMultSeqAlpha = 3
57
+ nMultSeqNumber = 3
58
+ nMultSeqSymbol = 3
59
+ nMultLength = 4
60
+ nMultNumber = 4
61
+ nMultSymbol = 6
62
+ nTmpAlphaUC = ""
63
+ nTmpAlphaLC = ""
64
+ nTmpNumber = ""
65
+ nTmpSymbol = ""
66
+ sAlphas = 'abcdefghijklmnopqrstuvwxyz'
67
+ sNumerics = '01234567890'
68
+ sSymbols = '!@#$%&/()+?*'
69
+ sComplexity = 'Invalid'
70
+ sStandards = 'Below'
71
+ nMinPwdLen = MIN_LENGTH
72
+
73
+ nScore = @password.length * nMultLength
74
+ nLength = @password.length
75
+ arrPwd = @password.gsub(/\s+/, "").split(/\s*/)
76
+ arrPwdLen = arrPwd.length
77
+
78
+ # Loop through password to check for Symbol, Numeric, Lowercase and Uppercase pattern matches
79
+ arrPwdLen.times do |a|
80
+ if /[A-Z]/.match(arrPwd[a])
81
+ if nTmpAlphaUC != ""
82
+ if (nTmpAlphaUC + 1) == a
83
+ nConsecAlphaUC += 1
84
+ nConsecCharType += 1
85
+ end
86
+ end
87
+ nTmpAlphaUC = a
88
+ nAlphaUC += 1
89
+ elsif /[a-z]/.match(arrPwd[a])
90
+ if nTmpAlphaLC != ""
91
+ if (nTmpAlphaLC + 1) == a
92
+ nConsecAlphaLC += 1
93
+ nConsecCharType += 1
94
+ end
95
+ end
96
+ nTmpAlphaLC = a
97
+ nAlphaLC += 1
98
+ elsif /[0-9]/.match(arrPwd[a])
99
+ if a > 0 && a < (arrPwdLen - 1)
100
+ nMidChar += 1
101
+ end
102
+ if nTmpNumber != ""
103
+ if (nTmpNumber + 1) == a
104
+ nConsecNumber += 1
105
+ nConsecCharType += 1
106
+ end
107
+ end
108
+ nTmpNumber = a
109
+ nNumber += 1
110
+ elsif /[^a-zA-Z0-9_]/.match(arrPwd[a])
111
+ if a > 0 && a < (arrPwdLen - 1)
112
+ nMidChar += 1
113
+ end
114
+ if nTmpSymbol != ""
115
+ if (nTmpSymbol + 1) == a
116
+ nConsecSymbol += 1
117
+ nConsecCharType += 1
118
+ end
119
+ end
120
+ nTmpSymbol = a
121
+ nSymbol += 1
122
+ end
123
+
124
+ # Internal loop through password to check for repeat characters
125
+ bCharExists = false
126
+ arrPwdLen.times do |b|
127
+ if arrPwd[a] == arrPwd[b] && a != b # repeat character exists
128
+ bCharExists = true
129
+ # Calculate increment deduction based on proximity to identical characters
130
+ # Deduction is incremented each time a new match is discovered
131
+ # Deduction amount is based on total password length divided by the
132
+ # difference of distance between currently selected match
133
+ nRepInc += (arrPwdLen / (b - a)).abs
134
+ end
135
+ end
136
+ if bCharExists
137
+ nRepChar += 1
138
+ nUnqChar = arrPwdLen - nRepChar
139
+ nRepInc = (nUnqChar > 0) ? (nRepInc / nUnqChar).ceil : nRepInc.ceil
140
+ end
141
+ end
142
+
143
+ # Check for sequential alpha string patterns (forward and reverse)
144
+ (sAlphas.size - 3).times do |s|
145
+ sFwd = sAlphas[s...s + 3]
146
+ sRev = sFwd.reverse
147
+ if @password.downcase.index(sFwd) || @password.downcase.index(sRev)
148
+ nSeqAlpha += 1
149
+ nSeqChar += 1
150
+ end
151
+ end
152
+
153
+ # Check for sequential numeric string patterns (forward and reverse)
154
+ (sNumerics.size - 3).times do |s|
155
+ sFwd = sNumerics[s...s + 3]
156
+ sRev = sFwd.reverse
157
+ if @password.downcase.index(sFwd) || @password.downcase.index(sRev)
158
+ nSeqNumber += 1
159
+ nSeqChar += 1
160
+ end
161
+ end
162
+
163
+ # Check for sequential symbol string patterns (forward and reverse)
164
+ (sSymbols.size - 3).times do |s|
165
+ sFwd = sSymbols[s...s + 3]
166
+ sRev = sFwd.reverse
167
+ if @password.downcase.index(sFwd) || @password.downcase.index(sRev)
168
+ nSeqSymbol += 1
169
+ nSeqChar += 1
170
+ end
171
+ end
172
+
173
+ # Modify overall score value based on usage vs requirements
174
+ if nAlphaUC > 0 && nAlphaUC < nLength
175
+ nScore += (nLength - nAlphaUC) * 2
176
+ end
177
+
178
+ if nAlphaLC > 0 && nAlphaLC < nLength
179
+ nScore += (nLength - nAlphaLC) * 2
180
+ end
181
+
182
+ if nNumber > 0 && nNumber < nLength
183
+ nScore += nNumber * nMultNumber
184
+ end
185
+
186
+ if nSymbol > 0
187
+ nScore += nSymbol * nMultSymbol
188
+ end
189
+
190
+ if nMidChar > 0
191
+ nScore += nMidChar * nMultMidChar
192
+ end
193
+
194
+ # Point deductions for poor practices
195
+ if (nAlphaLC > 0 || nAlphaUC > 0) && nSymbol == 0 && nNumber == 0 # Only Letters
196
+ nScore -= nLength
197
+ nAlphasOnly = nLength
198
+ end
199
+
200
+ if nAlphaLC === 0 && nAlphaUC === 0 && nSymbol === 0 && nNumber > 0 # Only Numbers
201
+ nScore -= nLength
202
+ nNumbersOnly = nLength
203
+ end
204
+
205
+ if nRepChar > 0 # Same character exists more than once
206
+ nScore -= nRepInc
207
+ end
208
+
209
+ if nConsecAlphaUC > 0 # Consecutive Uppercase Letters exist
210
+ nScore -= nConsecAlphaUC * nMultConsecAlphaUC
211
+ end
212
+
213
+ if nConsecAlphaLC > 0 # Consecutive Lowercase Letters exist
214
+ nScore -= nConsecAlphaLC * nMultConsecAlphaLC
215
+ end
216
+
217
+ if nConsecNumber > 0 # Consecutive Numbers exist
218
+ nScore -= nConsecNumber * nMultConsecNumber
219
+ end
220
+
221
+ if nSeqAlpha > 0 # Sequential alpha strings exist (3 characters or more)
222
+ nScore -= nSeqAlpha * nMultSeqAlpha
223
+ end
224
+
225
+ if nSeqNumber > 0 # Sequential numeric strings exist (3 character or more)
226
+ nScore -= nSeqNumber * nMultSeqNumber
227
+ end
228
+
229
+ if nSeqSymbol > 0 # Sequential symbol strings exist (3 character or more)
230
+ nScore -= nSeqSymbol * nMultSeqSymbol
231
+ end
232
+
233
+ # Determine if mandatory requirements have been met and set image indicators accordingly
234
+ arrChars = [nLength, nAlphaUC, nAlphaLC, nNumber, nSymbol]
235
+ arrCharsIds = ["nLength", "nAlphaUC", "nAlphaLC", "nNumber", "nSymbol"]
236
+ arrCharsLen = arrChars.length
237
+ arrCharsLen.times do |c|
238
+ minVal = arrCharsIds[c] == "nLength" ? MIN_LENGTH - 1 : 0
239
+ if arrChars[c] == (minVal + 1)
240
+ nReqChar += 1
241
+ elsif arrChars[c] > (minVal + 1)
242
+ nReqChar += 1
243
+ end
244
+ end
245
+ nRequirements = nReqChar
246
+ nMinReqChars = @password.length >= nMinPwdLen ? 3 : 4
247
+ if nRequirements > nMinReqChars # One or more required characters exist
248
+ nScore += (nRequirements * 2)
249
+ end
250
+
251
+ # Determine complexity based on overall score
252
+ if nScore > 100
253
+ nScore = 100
254
+ elsif nScore < 0
255
+ nScore = 0
256
+ end
257
+ @complexity = case nScore
258
+ when 0...20
259
+ "Trivial"
260
+ when 20...40
261
+ "Weak"
262
+ when 40...60
263
+ "Good"
264
+ when 60...80
265
+ "Strong"
266
+ else
267
+ "Very Strong"
268
+ end
269
+
270
+ @score = nScore
271
+ end
272
+
273
+ private
274
+
275
+ def check_minimum_requirements
276
+ if @password.length < MIN_LENGTH
277
+ @errors << "Password must be at least #{MIN_LENGTH} characters long"
278
+ return false
279
+ end
280
+ true
281
+ end
282
+ end
283
+ end
@@ -0,0 +1,29 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = %q{passgen}
5
+ s.version = "1.1.2"
6
+
7
+ s.required_rubygems_version = Gem::Requirement.new(">= 1.2") if s.respond_to? :required_rubygems_version=
8
+ s.authors = ["Erik Lindblad (CrypticE)", "Ronald Brachetti(rbecher)", "Ken Spencer (IotaSpencer)"]
9
+ s.date = %q{2020-03-31}
10
+ s.description = %q{A password generation gem for Ruby and Rails applications.}
11
+ s.email = %q{erik@l2c.se}
12
+ s.extra_rdoc_files = ["CHANGELOG", "README.rdoc", "lib/passgen.rb", "lib/passgen/probabilities.rb", "lib/passgen/strength_analyzer.rb"]
13
+ s.files = ["CHANGELOG", "Manifest", "README.rdoc", "Rakefile", "rails/init.rb", "lib/passgen.rb", "lib/passgen/probabilities.rb", "lib/passgen/strength_analyzer.rb", "passgen.gemspec", "spec/passgen/strength_analyzer_spec.rb", "spec/passgen_spec.rb"]
14
+ s.homepage = %q{http://github.com/cryptice/passgen}
15
+ s.rdoc_options = ["--line-numbers", "--inline-source", "--title", "Passgen", "--main", "README.rdoc"]
16
+ s.require_paths = ["lib"]
17
+ s.rubygems_version = %q{1.3.7}
18
+ s.summary = %q{A password generation gem for Ruby and Rails applications.}
19
+
20
+ if s.respond_to? :specification_version then
21
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
22
+ s.specification_version = 3
23
+
24
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
25
+ else
26
+ end
27
+ else
28
+ end
29
+ end
@@ -0,0 +1,2 @@
1
+ require 'passgen'
2
+
@@ -0,0 +1,75 @@
1
+ require "./lib/passgen"
2
+
3
+ describe "Using strength analyzer" do
4
+
5
+ before do
6
+ srand(2)
7
+ end
8
+
9
+ it "should return a StrengthAnalyzer instance" do
10
+ Passgen.analyze("abcdefg").should be_a(Passgen::StrengthAnalyzer)
11
+ end
12
+
13
+ it "should require minimum of 8 characters" do
14
+ sa = Passgen.analyze("abcdefg")
15
+ sa.score.should == 0
16
+ sa.complexity.should == "Invalid"
17
+ sa.errors.should == ["Password must be at least 8 characters long"]
18
+ end
19
+
20
+ it "should analyze aaaaaaaa correctly" do
21
+ sa = Passgen.analyze("aaaaaaaa")
22
+ sa.score.should == 0
23
+ sa.complexity.should == "Trivial"
24
+ sa.errors.should == []
25
+ end
26
+
27
+ it "should analyze aaaaAAAA correctly" do
28
+ sa = Passgen.analyze("aaaaAAAA")
29
+ sa.score.should == 0
30
+ sa.complexity.should == "Trivial"
31
+ sa.errors.should == []
32
+ end
33
+
34
+ it "should analyze aaaAAA11 correctly" do
35
+ sa = Passgen.analyze("aaaAAA11")
36
+ sa.score.should == 35
37
+ sa.complexity.should == "Weak"
38
+ sa.errors.should == []
39
+ end
40
+
41
+ it "should analyze aaa1AAA1 correctly" do
42
+ sa = Passgen.analyze("aaa1AAA1")
43
+ sa.score.should == 38
44
+ sa.complexity.should == "Weak"
45
+ sa.errors.should == []
46
+ end
47
+
48
+ it "should analyze hht14AAA correctly" do
49
+ sa = Passgen.analyze("hht14AAA")
50
+ sa.score.should == 57
51
+ sa.complexity.should == "Good"
52
+ sa.errors.should == []
53
+ end
54
+
55
+ it "should analyze hie14KOL correctly" do
56
+ sa = Passgen.analyze("hie14KOL")
57
+ sa.score.should == 62
58
+ sa.complexity.should == "Strong"
59
+ sa.errors.should == []
60
+ end
61
+
62
+ it "should analyze hI&14KoL correctly" do
63
+ sa = Passgen.analyze("hI&14KoL")
64
+ sa.score.should == 82
65
+ sa.complexity.should == "Very Strong"
66
+ sa.errors.should == []
67
+ end
68
+
69
+ it "should analyze hI&1#4KoL correctly" do
70
+ sa = Passgen.analyze("hI&1#4KoL")
71
+ sa.score.should == 100
72
+ sa.complexity.should == "Very Strong"
73
+ sa.errors.should == []
74
+ end
75
+ end
@@ -0,0 +1,150 @@
1
+ require "./lib/passgen"
2
+
3
+ describe "Using passgen" do
4
+
5
+ before do
6
+ srand(2)
7
+ end
8
+
9
+ it "should return password with default settings." do
10
+ Passgen::generate.should eql("OpTiwRslOh")
11
+ end
12
+
13
+ it "should return password with uppercase chars only" do
14
+ Passgen::generate(:uppercase => :only).should eql("IPNIWLSLIH")
15
+ end
16
+
17
+ it "should return password with lowercase chars only" do
18
+ Passgen::generate(:lowercase => :only).should eql("ipniwlslih")
19
+ end
20
+
21
+ it "should return password with digits only" do
22
+ Passgen::generate(:digits => :only).should eql("8862872154")
23
+ end
24
+
25
+ it "should return password with symbols only" do
26
+ Passgen::generate(:symbols => :only).should eql("))/*#*)(\#@")
27
+ end
28
+
29
+ it "should return password with lowercase and uppercase chars only" do
30
+ Passgen::generate(:digits => false).should eql("OpTiwRslOh")
31
+ end
32
+
33
+ it "should return password with lowercase and digit chars only" do
34
+ Passgen::generate(:uppercase => false).should eql("piwslh85lv")
35
+ end
36
+
37
+ it "should return password with lowercase and symbol chars only" do
38
+ Passgen::generate(:uppercase => false, :digits => false, :symbols => true).should eql("piwslh)&lv")
39
+ end
40
+
41
+ it "should return password with uppercase and digit chars only" do
42
+ Passgen::generate(:lowercase => false).should eql("PIWSLH85LV")
43
+ end
44
+
45
+ it "should return password with uppercase and symbol chars only" do
46
+ Passgen::generate(:lowercase => false, :digits => false, :symbols => true).should eql("PIWSLH)&LV")
47
+ end
48
+
49
+ it "should return password with digit and symbol chars only" do
50
+ Passgen::generate(:lowercase => false, :uppercase => false, :symbols => true).should eql("8&$8@)@872")
51
+ end
52
+
53
+ it "should return password with lowercase, uppercase and digit chars only" do
54
+ Passgen::generate.should eql("OpTiwRslOh")
55
+ end
56
+
57
+ it "should return password with lowercase, uppercase and symbol chars only" do
58
+ srand(3)
59
+ Passgen::generate(:digits => false, :symbols => true).should eql("Qy&d%iav+t")
60
+ end
61
+
62
+ it "should return password with lowercase, digit and symbol chars only" do
63
+ srand(4)
64
+ Passgen::generate(:uppercase => false, :symbols => true).should eql("?fb%xij$+4")
65
+ end
66
+
67
+ it "should return password with uppercase, digit and symbol chars only" do
68
+ srand(4)
69
+ Passgen::generate(:lowercase => false, :symbols => true).should eql("?FB%XIJ$+4")
70
+ end
71
+
72
+ it "should return given number of passwords in an Array" do
73
+ Passgen::generate(:number => 3).should eql(["OpTiwRslOh", "IXFlvVFAu8", "0LNdMeQRZN"])
74
+ end
75
+
76
+ it "should return a password with given length" do
77
+ Passgen::generate(:length => 8).should eql("OpTiwRsl")
78
+ end
79
+
80
+ it "should return several passwords of variable length" do
81
+ Passgen::generate(:length => 3..12, :number => 2).should eql(["pTiwRslOhIX", "VFAu80LN"])
82
+ end
83
+
84
+ it "should use given seed" do
85
+ pass1 = Passgen::generate(:seed => 5)
86
+ pass2 = Passgen::generate(:seed => 5)
87
+ pass1.should eql(pass2)
88
+ end
89
+
90
+ it "should set seed to Time.now + Process.id" do
91
+ pass1 = Passgen::generate(:seed => :default)
92
+ pass2 = Passgen::generate(:seed => :default)
93
+ pass1.should_not eql(pass2)
94
+ end
95
+
96
+ describe "handling tokens" do
97
+
98
+ it "should return a-z" do
99
+ Passgen::LOWERCASE_TOKENS.should eql(("a".."z").to_a)
100
+ end
101
+
102
+ it "should return A-Z" do
103
+ Passgen::UPPERCASE_TOKENS.should eql(("A".."Z").to_a)
104
+ end
105
+
106
+ it "should return 0-9" do
107
+ Passgen::DIGIT_TOKENS.should eql(("0".."9").to_a)
108
+ end
109
+
110
+ it "should return default symbols" do
111
+ Passgen::symbol_tokens.should eql(%w{! @ # $ % & / ( ) + ? *})
112
+ end
113
+
114
+ end
115
+
116
+ describe "pronounceable" do
117
+ it "should return a pronounceable lower case password" do
118
+ Passgen::generate(:pronounceable => true, :lowercase => :only).should == "ishanghter"
119
+ end
120
+
121
+ it "should return a pronounceable upper case password" do
122
+ Passgen::generate(:pronounceable => true, :uppercase => :only).should == "ISHANGHTER"
123
+ end
124
+
125
+ it "should return a pronounceable mixed case password" do
126
+ Passgen::generate(:pronounceable => true).should == "iShfIeRBAt"
127
+ end
128
+
129
+ it "should return a pronounceable mixed case password of length 7" do
130
+ Passgen::generate(:pronounceable => true, :length => 7).should == "IShfIeR"
131
+ end
132
+
133
+ it "should return a pronounceable password with 3 digits in front" do
134
+ Passgen::generate(:pronounceable => true, :digits_before => 3).should == "886uRApLIN"
135
+ end
136
+
137
+ it "should return a pronounceable password with default 2 digits in front" do
138
+ Passgen::generate(:pronounceable => true, :digits_before => true).should == "88mpICePED"
139
+ end
140
+
141
+ it "should return a pronounceable password with 3 digits at the end" do
142
+ Passgen::generate(:pronounceable => true, :digits_after => 3).should == "uRAPLIN886"
143
+ end
144
+
145
+ it "should return a pronounceable password with default 2 digits at the end" do
146
+ Passgen::generate(:pronounceable => true, :digits_after => true).should == "mPICEPED88"
147
+ end
148
+
149
+ end
150
+ end