pog19 1.1.1

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,291 @@
1
+ ##
2
+ # = random_string_generator.rb
3
+ #
4
+ # Copyright (c) 2007 Operis Systems, LLC
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+ #
18
+ require File.join(File.dirname(__FILE__), 'character_ranges.rb')
19
+
20
+ ##
21
+ # == RandomStringGenerator Class
22
+ #
23
+ # RandomStringGenerator provides the functionality to generate random Strings.
24
+ # It could be called RandomPasswordGenerator; however, this is not a highly secure
25
+ # generator. It depends on the security of the rand() method. An acceptable usage
26
+ # of this might be the generation of temporary passwords. See the SECURITY_NOTES file.
27
+ #
28
+ #
29
+ # === Generating a Random Password
30
+ #
31
+ # This is how to generate a random String with the default settings (see
32
+ # RandomStringGenerator.new for list of options)
33
+ #
34
+ # rsg = RandomStringGenerator.new
35
+ # str = rsg.generate => #<String>
36
+ #
37
+ # This will generate a random String of length 10, with no duplicate
38
+ # characters, and not using an uppercase characters.
39
+ #
40
+ # rsg = RandomStringGenerator.new( 10, :no_duplicates => true, :upper_alphas => false )
41
+ # str = rsg.generate => #<String>
42
+ #
43
+ #
44
+ # === Changing the length and options between generations
45
+ #
46
+ # rsg = RandomStringGenerator.new
47
+ # str = rsg.generate
48
+ #
49
+ # Now change the options and length and call generate
50
+ #
51
+ # rsg.set_options( :special => 'qwerty', :no_duplicates => true)
52
+ # rsg.length = 6
53
+ # str = rsg.generate
54
+ #
55
+ #
56
+ # === Generate a WEP key
57
+ #
58
+ # This will generate a 64 bit WEP key in hexidecimal format
59
+ #
60
+ # wep_64b_hex = RandomStringGenerator.generate_wep( 64 )
61
+ #
62
+ # This will generate a 128 bit WEP key in ASCII format. (Note that ASCII WEP
63
+ # keys are not as secure as hexidecimal keys)
64
+ #
65
+ # wep_128b_ascii = RandomStringGenerator.generate_wep( 128, :ascii )
66
+ #
67
+ #
68
+ # === Shuffle a String
69
+ #
70
+ # You can shuffle a string inplace (destructively) or you can shuffle the
71
+ # String into a new one.
72
+ #
73
+ # Inplace:
74
+ #
75
+ # str = 'foobar'
76
+ # RandomStringGenerator.shuffle_string! str
77
+ #
78
+ # +str+ is now shuffled.
79
+ #
80
+ # New String:
81
+ #
82
+ # str = 'foobar'
83
+ # new_str = RandomStringGenerator.shuffle_string str
84
+ #
85
+ # +str+ is not touched
86
+ # +new_str+ is a shuffled version of +str+
87
+ #
88
+ class RandomStringGenerator
89
+ include CharacterRanges
90
+
91
+ attr_accessor :length
92
+ attr_reader :options
93
+
94
+ ##
95
+ # Initializes a new random string generator.
96
+ #
97
+ # +len+ is the length of the string you wish to have generated. Default is 8.
98
+ #
99
+ # Options:
100
+ #
101
+ # <tt>:no_duplicates</tt>:: no duplicate characters, default is false
102
+ # <tt>:lower_alphas</tt>:: include lower alpha characters, default is true
103
+ # <tt>:upper_alphas</tt>:: include upper alpha characters, default is true
104
+ # <tt>:numerals</tt>:: include numeral characters, default is true
105
+ # <tt>:symbols</tt>:: include symbol characters, default is false
106
+ # <tt>:single_quotes</tt>:: include single quote characters, default is false
107
+ # <tt>:double_quotes</tt>:: include double quote characters, default is false
108
+ # <tt>:backtick</tt>:: include tick characters, default is false
109
+ # <tt>:special</tt>:: use a special string or array of ranges, overrides all other inclusion options
110
+ #
111
+ def initialize( len = 8, options = {} )
112
+ @length = len
113
+ @options = { :lower_alphas => true,
114
+ :upper_alphas => true,
115
+ :numerals => true,
116
+ :no_duplicates=> false}.merge options
117
+
118
+ end
119
+
120
+ ##
121
+ # Set the given options in
122
+ #
123
+ # See RandomStringGenerator.new for the list of options.
124
+ #
125
+ def set_option( opts = {} )
126
+ @options.merge!( opts )
127
+ end
128
+
129
+ ##
130
+ # Generate a password.
131
+ #
132
+ def generate
133
+ set_characters_array
134
+
135
+ # safety check for no_duplicates running out of characters
136
+ if @options[:no_duplicates] && @characters.length < @length
137
+ raise RuntimeError, "Too few options to have no duplicates."
138
+ end
139
+ shuffle_characters_array
140
+
141
+ # generate the password
142
+ p = String.new
143
+ for i in 0...@length
144
+ p << rand_char( @options[:noduplicates] )
145
+ end
146
+ return RandomStringGenerator.shuffle_string!(p)
147
+ end
148
+
149
+ ##
150
+ # Get a random character from the characters array. Set the parameter to
151
+ # +true+ to remove the character from the list after it's been returned.
152
+ #
153
+ def rand_char( delete_after_get = false )
154
+ i = rand_index
155
+ end_i = i + @characters.length
156
+
157
+ until @characters[ i % @characters.length ].ord > 0 do # Walk until you find a non-0 character
158
+ raise "No more characters." if i == end_i
159
+ i += 1
160
+ end
161
+ c = @characters[ i % @characters.length ].chr # c is now the character we will return
162
+ @characters[ i % @characters.length ] = 0.chr if delete_after_get # set the character to 0 (indicating deletedness)
163
+ return c
164
+ end
165
+
166
+ ##
167
+ # Shuffles the given string (destructively aka. inplace)
168
+ #
169
+ def self.shuffle_string!( str )
170
+ scramblers = Array.new(8)
171
+ for i in 0...(str.length/2)
172
+ scrambler_1 = rand(0xffffffff)
173
+ scrambler_2 = rand(0xffffffff)
174
+ for j in 0...4
175
+ scramblers[j] = ((scrambler_1 & (0xff<<j*8)) >> j*8) % str.length
176
+ end
177
+ for j in 4...8
178
+ scramblers[j] = ((scrambler_2 & (0xff<<(j-4)*8)) >> (j-4)*8) % str.length
179
+ # offset the wrap around to the end of the string
180
+ scramblers[j] = (scramblers[j] + (0xff%str.length)) % str.length
181
+ end
182
+
183
+ # do the scrambling
184
+ t = str[scramblers[0]]
185
+ 7.times { |j| str[scramblers[j]] = str[scramblers[j+1]] }
186
+ str[scramblers[7]] = t
187
+ end
188
+ return str
189
+ end
190
+
191
+ ##
192
+ # Returns a shuffled version of the given string.
193
+ #
194
+ def self.shuffle_string( str )
195
+ RandomStringGenerator.shuffle_string!( str.dup )
196
+ end
197
+
198
+ ##
199
+ # Scramble the characters array
200
+ #
201
+ def shuffle_characters_array
202
+ RandomStringGenerator.shuffle_string!( @characters )
203
+ end
204
+
205
+ ##
206
+ # Set the characters array to the set that we can use to generate a new
207
+ # password.
208
+ #
209
+ def set_characters_array
210
+ if @options.has_key? :special # set to special if it's there
211
+ set_characters_from_special( @options[:special] )
212
+ else # set by the given ranges
213
+ ranges = Array.new
214
+ @options.each do |opt, val| # run through options
215
+ unless opt == :length || opt == :no_duplicates || val == false
216
+ ranges << self.send("range_#{opt.to_s}")
217
+ end
218
+ end
219
+ set_characters_from_ranges ranges.flatten
220
+ end
221
+ end
222
+
223
+
224
+ ##
225
+ # Generates a WEP key
226
+ #
227
+ # +bits+: The number of bits for the encryption policy (ie. 64, 128, 152, 256)
228
+ # +output+:: Either <tt>:hex</tt> or <tt>:ascii</tt> (hex is more secure)
229
+ #
230
+ def self.generate_wep( bits, output = :hex )
231
+ raise "Not a valid number of bits for a WEP key." unless [64,128,152,256].include? bits
232
+
233
+ case output
234
+ when :hex
235
+ rsg = RandomStringGenerator.new((bits-24)/8, :special => [0..0xff])
236
+ key = rsg.generate
237
+ return key.unpack("H#{key.length*2}").at(0) if output == :hex
238
+ when :ascii
239
+ rsg = RandomStringGenerator.new((bits-24)/8, :special => [0x20..0x7e])
240
+ key = rsg.generate
241
+ return key
242
+ else
243
+ raise "Invalid output type."
244
+ end
245
+ end
246
+
247
+ protected
248
+
249
+ ##
250
+ # Get a random index of the characters array
251
+ #
252
+ def rand_index
253
+ j = rand(@characters.length**4)
254
+ i = 0
255
+ for k in 0...4
256
+ i += (j & (0xff<<k*8)) >> k*8
257
+ end
258
+ i %= @characters.length
259
+ end
260
+
261
+ ##
262
+ # Set the characters array using the :special options variable to the set we
263
+ # can use to generate a new pasword.
264
+ #
265
+ def set_characters_from_special( special )
266
+ if special.is_a? Array # check that each value of the array is a range
267
+ special.each do |r|
268
+ raise ArgumentError, ":special must either be a String or an Array of Ranges." unless r.is_a? Range
269
+ end
270
+ set_characters_from_ranges special
271
+ elsif special.is_a? String
272
+ @characters = special
273
+ else # must either be a string or array of ranges
274
+ raise ArgumentError, ":special must either be a String or an Array of Ranges."
275
+ end
276
+ end
277
+
278
+ ##
279
+ # Set the characters array by the @ranges variable to the set that we can
280
+ # use to generate a new password.
281
+ #
282
+ def set_characters_from_ranges( ranges )
283
+ @characters = String.new
284
+ ranges.each do |r|
285
+ for i in r
286
+ @characters << i.chr
287
+ end
288
+ end
289
+ @characters
290
+ end
291
+ end
data/lib/salt.rb ADDED
@@ -0,0 +1,135 @@
1
+ ##
2
+ # = salt.rb
3
+ #
4
+ # Copyright (c) 2007 Operis Systems, LLC
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+ #
18
+ require File.join(File.dirname(__FILE__), 'random_string_generator.rb')
19
+
20
+ ##
21
+ # == Salt Class
22
+ #
23
+ # Salt encapsulates a password's salt and provides functionality for password
24
+ # salting and random salt generation.
25
+ #
26
+ #
27
+ # === What is a "password salt"?
28
+ #
29
+ # A "password salt" is a string that is added to a password string to obscure
30
+ # the password when it is hashed to keep the password hash from being cracked.
31
+ #
32
+ # See http://en.wikipedia.org/wiki/Salt_(cryptography)
33
+ #
34
+ #
35
+ # === Setting up a new Salt
36
+ #
37
+ # This generates a new, random salt, with end placement
38
+ #
39
+ # s = Salt.new
40
+ #
41
+ # This creates the salt 'foobar', with split placement.
42
+ #
43
+ # s = Salt.new('foobar')
44
+ #
45
+ # This generates a new, random salt of length 9, with end placement
46
+ #
47
+ # s = Salt.new( :new, :end, :length => 9)
48
+ # or
49
+ # s = Salt.new( Salt.generate( 8 ) )
50
+ #
51
+ #
52
+ # === Salting a password
53
+ #
54
+ # This will salt the password 'foobar'
55
+ #
56
+ # s = s.salt('foobar')
57
+ #
58
+ #
59
+ # === What is salt placement?
60
+ #
61
+ # "salt placement" is where the salt will go in the password. For example,
62
+ # "beginning" salt placement would prepend the salt to the password prior to
63
+ # hashing, and "split" salt placement would prepend the first half and
64
+ # append the second half.
65
+ #
66
+ class Salt
67
+ attr_accessor :string
68
+ attr_reader :placement
69
+
70
+ ##
71
+ # Initializes a new Salt instance with the given +string+ and +placement+
72
+ #
73
+ # See Salt.placement= for +placement+ options
74
+ #
75
+ # Options:
76
+ #
77
+ # <tt>:length</tt>: the length for a new salt. (default is 8)
78
+ #
79
+ def initialize( string = :new, placement = :end, options = {} )
80
+ @placement = placement.to_sym
81
+ @string = (string == :new ?
82
+ Salt.generate_str(options[:length] || 8) : string)
83
+ end
84
+
85
+ ##
86
+ # Set the salt's placement
87
+ #
88
+ # Valid placements: <tt>:end</tt> (default), <tt>:beginning</tt>, <tt>:split</tt>
89
+ #
90
+ def placement=( new_placement )
91
+ @placement = new_placement.to_sym
92
+ end
93
+
94
+ ##
95
+ # Returns the given password, salted.
96
+ #
97
+ def salt_password( password )
98
+ case placement.to_sym
99
+ when :end
100
+ password.to_s + string
101
+ when :beginning
102
+ string + password.to_s
103
+ when :split
104
+ string[0...(string.length/2).floor] + password.to_s + string[(string.length/2).floor...string.length]
105
+ else
106
+ raise RuntimeError, "#{placement.to_s} is an invalid salt placement."
107
+ end
108
+ end
109
+
110
+ ##
111
+ # Returns the salt string.
112
+ #
113
+ # Same as calling Salt#string
114
+ #
115
+ def to_s
116
+ string.to_s
117
+ end
118
+
119
+ ##
120
+ # Generate a salt string of the given length (default is 8).
121
+ #
122
+ def self.generate_str(length = 8)
123
+ RandomStringGenerator.new( length, { :lower_alphas => true,
124
+ :upper_alphas => true,
125
+ :numerals => true,
126
+ :symbols => true }).generate
127
+ end
128
+
129
+ ##
130
+ # Returns +true+ if the given salt is equivilent to this salt, +false+ otherwise
131
+ #
132
+ def eql?( salt )
133
+ self.to_s.eql?(salt.to_s) && self.placement.eql?(salt.placement)
134
+ end
135
+ end
data/lib/string.rb ADDED
@@ -0,0 +1,15 @@
1
+ ##
2
+ # Add the sanitize! method to the String class
3
+ #
4
+ class String
5
+
6
+ ##
7
+ # Sanitize the String *completely* from memory. This is non-reversible.
8
+ #
9
+ def sanitize
10
+ for i in 0...self.length
11
+ self[i] = 0
12
+ end
13
+ self.delete!("\000")
14
+ end
15
+ end
data/pog1-9.gemspec ADDED
@@ -0,0 +1,20 @@
1
+ require 'rubygems'
2
+
3
+ version = File.read('VERSION').strip
4
+ raise "no version" if version.empty?
5
+
6
+ spec = Gem::Specification.new do |s|
7
+ s.name = 'pog19'
8
+ s.version = version
9
+ s.author = 'Operis Systems, LLC'
10
+ s.email = ''
11
+ s.homepage = 'http://pog.rubyforge.org/'
12
+ s.platform = Gem::Platform::RUBY
13
+ s.summary = 'A Ruby gem for simplifying random password generation'
14
+ s.description = s.summary + ', password strength testing, password hashing and salting, and password-hash authentication.'
15
+ s.files = Dir['**/*'].delete_if { |f| f =~ /(cvs|gem|svn)$/i }
16
+ s.require_path = 'lib'
17
+ s.rdoc_options << '--all' << '--inline-source' << '--main' << 'lib/*.rb'
18
+ s.has_rdoc = true
19
+ s.rubyforge_project = 'pog'
20
+ end
@@ -0,0 +1,122 @@
1
+ # Copyright (c) 2007 Operis Systems, LLC
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+ require 'test/unit'
15
+ require 'base64'
16
+ require File.join( File.dirname(__FILE__), '..', 'lib', 'pog.rb' )
17
+
18
+ TEST_PASS = '/O0op;G75wu3saMv|N9&DA?I*/!3-gqHUe9{VZL{M3YVoYUQ]6q{]Poz%c)V#T,i=g.G8>&'
19
+
20
+ class TC_Password < Test::Unit::TestCase
21
+ def test_to_s
22
+ password = Password.new( TEST_PASS )
23
+ assert_equal(TEST_PASS, password.to_s)
24
+
25
+ password = Password.new( :new )
26
+ assert_equal false, password.to_s.empty?
27
+ end
28
+
29
+ def test_hash
30
+ pass = Password.new( TEST_PASS )
31
+
32
+ # test digests
33
+ assert_equal Digest::SHA256.digest(TEST_PASS),
34
+ pass.hash,
35
+ 'hash generation failed for SHA256 digest'
36
+
37
+ pass.digest = :sha512
38
+ assert_equal Digest::SHA512.digest(TEST_PASS),
39
+ pass.hash,
40
+ 'hash generation failed for SHA512 digest'
41
+
42
+ pass.digest = :sha384
43
+ assert_equal Digest::SHA384.digest(TEST_PASS),
44
+ pass.hash,
45
+ 'hash generation failed for SHA384 digest'
46
+
47
+ pass.digest = :sha1
48
+ assert_equal Digest::SHA1.digest(TEST_PASS),
49
+ pass.hash,
50
+ 'hash generation failed for SHA1 digest'
51
+
52
+ pass.digest = :md5
53
+ assert_equal Digest::MD5.digest(TEST_PASS),
54
+ pass.hash,
55
+ 'hash generation failed for MD5 digest'
56
+
57
+ # test formats
58
+ pass.digest = :sha256
59
+
60
+ assert_equal Digest::SHA256.hexdigest(TEST_PASS),
61
+ pass.hash( :hex ),
62
+ 'hash generation failed for default (hexidecimal) format'
63
+
64
+ assert_equal Base64.encode64( Digest::SHA256.digest(TEST_PASS) ).gsub(/\s+/, ''),
65
+ pass.hash( :base64 ),
66
+ 'hash generation failed for base64 format'
67
+ end
68
+
69
+ def test_authenticate
70
+ pass = Password.new( TEST_PASS, Salt.new )
71
+
72
+ assert pass.authenticate(Digest::SHA256.digest(TEST_PASS + pass.salt.to_s))
73
+ assert_equal false, pass.authenticate(Digest::SHA256.digest(pass.salt.to_s + TEST_PASS))
74
+
75
+ assert pass.authenticate(Digest::SHA1.digest(TEST_PASS + pass.salt.to_s))
76
+ assert pass.authenticate(Digest::SHA384.hexdigest(TEST_PASS + pass.salt.to_s))
77
+ assert pass.authenticate(Digest::SHA512.digest(TEST_PASS + pass.salt.to_s))
78
+
79
+ md5_b64 = [Digest::MD5.digest(TEST_PASS + pass.salt.to_s)].pack('m').gsub(/\s+/, '')
80
+ assert pass.authenticate( md5_b64 )
81
+
82
+ assert_raise(RuntimeError) {pass.authenticate(md5_b64+'#')}
83
+ end
84
+
85
+ def test_setters
86
+ salt = Salt.new
87
+ p = Password.new :new, Salt.new, :sha512
88
+
89
+ p.password = TEST_PASS
90
+ assert_same(p.password, TEST_PASS)
91
+
92
+ p.salt = salt
93
+ assert_same(p.salt, salt)
94
+
95
+ p.salt = salt.to_s
96
+ assert(p.salt.eql?(salt))
97
+
98
+ assert_raise(ArgumentError) { p.hash(:foo) }
99
+
100
+ assert_raise(RuntimeError) { p.digest = :foo }
101
+ end
102
+
103
+ def test_sanitize
104
+ test_pass = TEST_PASS.dup
105
+ p = Password.new( test_pass )
106
+ assert_equal(nil, p.sanitize)
107
+
108
+ assert_equal('', p.to_s)
109
+ assert_equal(nil, p.password)
110
+
111
+ assert_equal Digest::SHA256.digest(TEST_PASS),
112
+ p.hash
113
+ assert_same(p.password, nil)
114
+ assert_equal(test_pass, '')
115
+
116
+ for i in 0...test_pass.length
117
+ assert_equal test_pass[i], 0
118
+ end
119
+
120
+ assert_raise(RuntimeError) {p.rehash}
121
+ end
122
+ end