is_passgen 1.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,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