xchange-passgen 1.0.1.a
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/CHANGELOG +13 -0
- data/Manifest +11 -0
- data/README.rdoc +156 -0
- data/Rakefile +15 -0
- data/init.rb +2 -0
- data/lib/passgen/probabilities.rb +804 -0
- data/lib/passgen/strength_analyzer.rb +282 -0
- data/lib/passgen.rb +328 -0
- data/passgen.gemspec +30 -0
- data/spec/passgen/strength_analyzer_spec.rb +75 -0
- data/spec/passgen_spec.rb +150 -0
- metadata +63 -0
@@ -0,0 +1,282 @@
|
|
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
|
+
def check_minimum_requirements
|
275
|
+
if @password.length < MIN_LENGTH
|
276
|
+
@errors << "Password must be at least #{MIN_LENGTH} characters long"
|
277
|
+
return false
|
278
|
+
end
|
279
|
+
true
|
280
|
+
end
|
281
|
+
end
|
282
|
+
end
|
data/lib/passgen.rb
ADDED
@@ -0,0 +1,328 @@
|
|
1
|
+
#= Passgen
|
2
|
+
#
|
3
|
+
#Ruby gem for generating passwords quickly and easily. Although it is
|
4
|
+
#suitable for use within Rails it has no Rails dependencies and can be used in
|
5
|
+
#non-Rails applications as well.
|
6
|
+
#
|
7
|
+
#== Install
|
8
|
+
#
|
9
|
+
# gem install cryptice-passgen --source http://gems.github.com
|
10
|
+
#
|
11
|
+
#== Usage
|
12
|
+
#
|
13
|
+
#The usage could not be easier. Just require and call the generate method:
|
14
|
+
#
|
15
|
+
# >> require 'rubygems'
|
16
|
+
# >> require 'passgen'
|
17
|
+
# >> Passgen::generate
|
18
|
+
# => "zLWCeS3xC9"
|
19
|
+
#
|
20
|
+
#== Examples
|
21
|
+
#
|
22
|
+
# >> Passgen::generate
|
23
|
+
# => "zLWCeS3xC9"
|
24
|
+
#
|
25
|
+
# >> Passgen::generate(:length => 20)
|
26
|
+
# => "6lCcHvkuEW6OuzAtkoAs"
|
27
|
+
#
|
28
|
+
# >> Passgen::generate(:symbols => true)
|
29
|
+
# => "gr)$6bIym1"
|
30
|
+
#
|
31
|
+
# >> Passgen::generate(:lowercase => :only)
|
32
|
+
# => "ysbwuxbcea"
|
33
|
+
#
|
34
|
+
# >> Passgen::generate(:number => 3)
|
35
|
+
# => ["REdOigTkdI", "PQu8DsV9WZ", "qptKLbw8YQ"]
|
36
|
+
#
|
37
|
+
# >> Passgen::generate(:seed => 5)
|
38
|
+
# => "JoV9M2qjiK"
|
39
|
+
# >> Passgen::generate(:seed => 5) # Will generate same password again
|
40
|
+
# => "JoV9M2qjiK"
|
41
|
+
#
|
42
|
+
# >> Passgen::generate(:seed => :default) # Will set random seed...
|
43
|
+
# => "SI8QDBdV98"
|
44
|
+
# >> Passgen::generate(:seed => :default) # and hence give different password
|
45
|
+
# => "tHHU5HLBAn"
|
46
|
+
#
|
47
|
+
#== Options:
|
48
|
+
#
|
49
|
+
#=== :lowercase => true/false/:only
|
50
|
+
#* true - Use lowercase letters in the generated password.
|
51
|
+
#* false - Do not use lowercase letters in the generated password.
|
52
|
+
#* :only - Only use lowercase letters in the generated password.
|
53
|
+
#
|
54
|
+
#=== :uppercase => true/false/:only
|
55
|
+
#* true - Use uppercase letters in the generated password.
|
56
|
+
#* false - Do not use uppercase letters in the generated password.
|
57
|
+
#* :only - Only use uppercase letters in the generated password.
|
58
|
+
#
|
59
|
+
#=== :digits => true/false/:only
|
60
|
+
#* true - Use digits in the generated password.
|
61
|
+
#* false - Do not use digits in the generated password.
|
62
|
+
#* :only - Only use digits in the generated password.
|
63
|
+
#
|
64
|
+
#=== :symbols => true/false/:only/:list
|
65
|
+
#* true - Use symbols in the generated password.
|
66
|
+
#* false - Do not use symbols in the generated password.
|
67
|
+
#* :only - Only use symbols in the generated password.
|
68
|
+
#* :list - A string with the symbols to use. Not implemented yet.
|
69
|
+
#
|
70
|
+
#=== :pronounceable => true/false
|
71
|
+
#Not implmented yet.
|
72
|
+
#
|
73
|
+
#=== :number => integer
|
74
|
+
#Number of passwords to generate. If >1 the result is an Array.
|
75
|
+
#
|
76
|
+
#=== :length => integer/range
|
77
|
+
#The number of characters in the generated passwords. A range results in passwords
|
78
|
+
#lengths within the given range.
|
79
|
+
#
|
80
|
+
#=== :seed => integer/:default
|
81
|
+
#Set the srand seed to the given integer prior to generating the passwords.
|
82
|
+
#
|
83
|
+
#=== Default values:
|
84
|
+
#
|
85
|
+
#:lowercase => true
|
86
|
+
#
|
87
|
+
#:uppercase => true
|
88
|
+
#
|
89
|
+
#:digits => true
|
90
|
+
#
|
91
|
+
#:symbols => false
|
92
|
+
#
|
93
|
+
#:pronounceable => Not implemented yet.
|
94
|
+
#
|
95
|
+
#:number => 1
|
96
|
+
#
|
97
|
+
#:length => 10
|
98
|
+
#
|
99
|
+
#:seed => nil
|
100
|
+
#
|
101
|
+
#== Copyright and license
|
102
|
+
#
|
103
|
+
#Copyright (c) 2009 Erik Lindblad
|
104
|
+
#
|
105
|
+
#Permission is hereby granted, free of charge, to any person obtaining
|
106
|
+
#a copy of this software and associated documentation files (the
|
107
|
+
#"Software"), to deal in the Software without restriction, including
|
108
|
+
#without limitation the rights to use, copy, modify, merge, publish,
|
109
|
+
#distribute, sublicense, and/or sell copies of the Software, and to
|
110
|
+
#permit persons to whom the Software is furnished to do so, subject to
|
111
|
+
#the following conditions:
|
112
|
+
#
|
113
|
+
#The above copyright notice and this permission notice shall be
|
114
|
+
#included in all copies or substantial portions of the Software.
|
115
|
+
#
|
116
|
+
#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
117
|
+
#EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
118
|
+
#MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
119
|
+
#NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
120
|
+
#LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
121
|
+
#OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
122
|
+
#WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
123
|
+
|
124
|
+
require 'digest'
|
125
|
+
require 'passgen/probabilities'
|
126
|
+
require 'passgen/strength_analyzer'
|
127
|
+
|
128
|
+
module Passgen
|
129
|
+
|
130
|
+
VERSION = "1.0.0"
|
131
|
+
|
132
|
+
DEFAULT_PARAMS = {
|
133
|
+
:number => 1,
|
134
|
+
:length => 10,
|
135
|
+
:lowercase => true,
|
136
|
+
:uppercase => true,
|
137
|
+
:digits => true,
|
138
|
+
:symbols => false,
|
139
|
+
:pronounceable => false
|
140
|
+
}
|
141
|
+
|
142
|
+
def self.default_seed
|
143
|
+
Digest::MD5.hexdigest("#{rand}#{Time.now}#{Process.object_id}").to_i(16)
|
144
|
+
end
|
145
|
+
|
146
|
+
def self.generate(params={})
|
147
|
+
set_options(params)
|
148
|
+
tokens = valid_tokens
|
149
|
+
set_seed
|
150
|
+
|
151
|
+
if n == 1
|
152
|
+
generate_one(tokens)
|
153
|
+
else
|
154
|
+
Array.new(n) {|i| generate_one(tokens) }
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
def self.analyze(pw)
|
159
|
+
Passgen::StrengthAnalyzer.analyze(pw)
|
160
|
+
end
|
161
|
+
|
162
|
+
private
|
163
|
+
def self.alphabet(index)
|
164
|
+
if use_lowercase? && !use_uppercase?
|
165
|
+
LOWERCASE_TOKENS[index]
|
166
|
+
elsif use_uppercase? && !use_lowercase?
|
167
|
+
UPPERCASE_TOKENS[index]
|
168
|
+
else
|
169
|
+
tmp = LOWERCASE_TOKENS[index]
|
170
|
+
tmp.upcase! if rand > 0.5
|
171
|
+
tmp
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
def self.generate_one(tokens)
|
176
|
+
if @options[:pronounceable]
|
177
|
+
generate_pronounceable
|
178
|
+
else
|
179
|
+
Array.new(password_length) {tokens[rand(tokens.size)]}.join
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
def self.generate_pronounceable
|
184
|
+
password = ""
|
185
|
+
|
186
|
+
# Append digits in front
|
187
|
+
digits_prefix = if @options[:digits_before]
|
188
|
+
@options[:length] -= @options[:digits_before]
|
189
|
+
Array.new(@options[:digits_before]) {DIGIT_TOKENS[rand(DIGIT_TOKENS.size)]}.join
|
190
|
+
else
|
191
|
+
""
|
192
|
+
end
|
193
|
+
|
194
|
+
# Append digits at the end
|
195
|
+
digits_suffix = if @options[:digits_after]
|
196
|
+
@options[:length] -= @options[:digits_after]
|
197
|
+
Array.new(@options[:digits_after]) {DIGIT_TOKENS[rand(DIGIT_TOKENS.size)]}.join
|
198
|
+
else
|
199
|
+
""
|
200
|
+
end
|
201
|
+
|
202
|
+
# Find a random starting point.
|
203
|
+
found_start = false
|
204
|
+
ranno = rand * SIGMA # random number [0,1[ weighed by sum of frequencies
|
205
|
+
sum = 0;
|
206
|
+
N_LETTERS.times do |c1|
|
207
|
+
N_LETTERS.times do |c2|
|
208
|
+
N_LETTERS.times do |c3|
|
209
|
+
sum += P[c1][c2][c3]
|
210
|
+
if sum > ranno
|
211
|
+
password << alphabet(c1)
|
212
|
+
password << alphabet(c2)
|
213
|
+
password << alphabet(c3)
|
214
|
+
found_start = true
|
215
|
+
break
|
216
|
+
end
|
217
|
+
end
|
218
|
+
break if found_start
|
219
|
+
end
|
220
|
+
break if found_start
|
221
|
+
end
|
222
|
+
|
223
|
+
# Do a random walk.
|
224
|
+
(3...@options[:length]).each do |nchar|
|
225
|
+
c1 = LETTER_INDEXES[password[nchar-2..nchar-2]]
|
226
|
+
c2 = LETTER_INDEXES[password[nchar-1..nchar-1]]
|
227
|
+
sum = 0
|
228
|
+
N_LETTERS.times {|c3| sum += P[c1][c2][c3]}
|
229
|
+
break if sum == 0
|
230
|
+
ranno = rand * sum
|
231
|
+
sum = 0;
|
232
|
+
N_LETTERS.times do |c3|
|
233
|
+
sum += P[c1][c2][c3]
|
234
|
+
if sum > ranno
|
235
|
+
password << alphabet(c3)
|
236
|
+
break
|
237
|
+
end
|
238
|
+
end
|
239
|
+
end
|
240
|
+
digits_prefix + password + digits_suffix
|
241
|
+
end
|
242
|
+
|
243
|
+
def self.password_length
|
244
|
+
if @options[:length].is_a?(Range)
|
245
|
+
tmp = @options[:length].to_a
|
246
|
+
tmp[rand(tmp.size)]
|
247
|
+
else
|
248
|
+
@options[:length].to_i
|
249
|
+
end
|
250
|
+
end
|
251
|
+
|
252
|
+
def self.n
|
253
|
+
@options[:number]
|
254
|
+
end
|
255
|
+
|
256
|
+
def self.set_options(params)
|
257
|
+
if params[:lowercase] == :only
|
258
|
+
params[:uppercase] = false
|
259
|
+
params[:digits] = false
|
260
|
+
end
|
261
|
+
|
262
|
+
if params[:uppercase] == :only
|
263
|
+
params[:lowercase] = false
|
264
|
+
params[:digits] = false
|
265
|
+
end
|
266
|
+
|
267
|
+
if params[:digits] == :only
|
268
|
+
params[:lowercase] = false
|
269
|
+
params[:uppercase] = false
|
270
|
+
end
|
271
|
+
|
272
|
+
if params[:symbols] == :only
|
273
|
+
params[:lowercase] = false
|
274
|
+
params[:uppercase] = false
|
275
|
+
params[:digits] = false
|
276
|
+
params[:symbols] = true
|
277
|
+
end
|
278
|
+
|
279
|
+
if params[:digits_before] == true
|
280
|
+
params[:digits_before] = 2
|
281
|
+
end
|
282
|
+
|
283
|
+
if params[:digits_after] == true
|
284
|
+
params[:digits_after] = 2
|
285
|
+
end
|
286
|
+
|
287
|
+
@options = DEFAULT_PARAMS.merge(params)
|
288
|
+
end
|
289
|
+
|
290
|
+
def self.set_seed
|
291
|
+
if @options[:seed]
|
292
|
+
if @options[:seed] == :default
|
293
|
+
srand(default_seed)
|
294
|
+
else
|
295
|
+
srand(@options[:seed])
|
296
|
+
end
|
297
|
+
end
|
298
|
+
end
|
299
|
+
|
300
|
+
def self.symbol_tokens
|
301
|
+
%w{! @ # $ % & / ( ) + ? *}
|
302
|
+
end
|
303
|
+
|
304
|
+
def self.use_lowercase?
|
305
|
+
@options[:lowercase]
|
306
|
+
end
|
307
|
+
|
308
|
+
def self.use_uppercase?
|
309
|
+
@options[:uppercase]
|
310
|
+
end
|
311
|
+
|
312
|
+
def self.use_digits?
|
313
|
+
@options[:digits]
|
314
|
+
end
|
315
|
+
|
316
|
+
def self.use_symbols?
|
317
|
+
@options[:symbols]
|
318
|
+
end
|
319
|
+
|
320
|
+
def self.valid_tokens
|
321
|
+
tmp = []
|
322
|
+
tmp += LOWERCASE_TOKENS if use_lowercase?
|
323
|
+
tmp += UPPERCASE_TOKENS if use_uppercase?
|
324
|
+
tmp += DIGIT_TOKENS if use_digits?
|
325
|
+
tmp += symbol_tokens if use_symbols?
|
326
|
+
tmp
|
327
|
+
end
|
328
|
+
end
|
data/passgen.gemspec
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
Gem::Specification.new do |s|
|
4
|
+
s.name = %q{xchange-passgen}
|
5
|
+
s.version = "1.0.1.a"
|
6
|
+
|
7
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 1.2") if s.respond_to? :required_rubygems_version=
|
8
|
+
s.authors = ["Erik Lindblad"]
|
9
|
+
s.date = %q{2010-12-16}
|
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", "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.rubyforge_project = %q{passgen}
|
18
|
+
s.rubygems_version = %q{1.3.7}
|
19
|
+
s.summary = %q{A password generation gem for Ruby and Rails applications.}
|
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::VERSION) >= Gem::Version.new('1.2.0') then
|
26
|
+
else
|
27
|
+
end
|
28
|
+
else
|
29
|
+
end
|
30
|
+
end
|
@@ -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
|