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.
- data/CHANGELOG +45 -0
- data/LICENSE +201 -0
- data/NOTICE +12 -0
- data/README +66 -0
- data/Rakefile +121 -0
- data/SECURITY +36 -0
- data/VERSION +1 -0
- data/lib/character_ranges.rb +185 -0
- data/lib/encoding_translation.rb +53 -0
- data/lib/password.rb +329 -0
- data/lib/password_tests.rb +415 -0
- data/lib/pog.rb +21 -0
- data/lib/random_string_generator.rb +291 -0
- data/lib/salt.rb +135 -0
- data/lib/string.rb +15 -0
- data/pog1-9.gemspec +20 -0
- data/test/tc_password.rb +122 -0
- data/test/tc_password_tests.rb +209 -0
- data/test/tc_random_string_generator.rb +133 -0
- data/test/tc_salt.rb +78 -0
- metadata +84 -0
@@ -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
|
data/test/tc_password.rb
ADDED
@@ -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
|