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,53 @@
1
+ ##
2
+ # = encoding_translation.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
+
19
+ ##
20
+ # == EncodingTranslation Module
21
+ #
22
+ # This module is a set of encoding translations shortcut methods using the
23
+ # String#pack and String#unpack methods.
24
+ #
25
+ module EncodingTranslation
26
+ ##
27
+ # Converts a binary string +str+ to hexidecimal
28
+ #
29
+ def bin_to_hex( str )
30
+ str.unpack("H#{str.length*2}").at(0)
31
+ end
32
+
33
+ ##
34
+ # Converts a hexidecimal string +str+ to binary
35
+ #
36
+ def hex_to_bin( str )
37
+ [str].pack("H#{str.length}")
38
+ end
39
+
40
+ ##
41
+ # Converts a binary string +str+ to base 64
42
+ #
43
+ def bin_to_b64( str )
44
+ [str].pack('m').gsub(/\s+/, '')
45
+ end
46
+
47
+ ##
48
+ # Converts a base64 string +str+ to binary
49
+ #
50
+ def b64_to_bin( str )
51
+ str.unpack("m").at(0)
52
+ end
53
+ end
data/lib/password.rb ADDED
@@ -0,0 +1,329 @@
1
+ ##
2
+ # = password.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 'digest/md5'
19
+ require 'digest/sha1'
20
+ require 'digest/sha2'
21
+ require File.join(File.dirname(__FILE__), 'salt.rb')
22
+ require File.join(File.dirname(__FILE__), 'string.rb')
23
+ require File.join(File.dirname(__FILE__), 'random_string_generator.rb')
24
+ require File.join(File.dirname(__FILE__), 'encoding_translation.rb')
25
+
26
+ ##
27
+ # == Password Class
28
+ #
29
+ # Password encapsulates a password and provides functionality for hashing,
30
+ # hash-based authentication, and random password generation.
31
+ #
32
+ #
33
+ # === Setting up a new Password object
34
+ #
35
+ # This will create a new Password object using no salt and the default, SHA256
36
+ # digest.
37
+ #
38
+ # p = Password.new( 'foobar' )
39
+ #
40
+ # This will create a new Password object using a new, random salt and the
41
+ # SHA512 digest.
42
+ #
43
+ # p = Password.new( 'foobar', Salt.new, :sha512 )
44
+ #
45
+ # This will create a new, random password (using default random generation
46
+ # options -- see Password.new) using no salt, and the default, SHA256
47
+ # digest.
48
+ #
49
+ # p = Password.new( :new )
50
+ #
51
+ # This will create a new, random password of length 10 (and using default
52
+ # random generation options -- see Password.new) using no salt, and the
53
+ # MD5 digest.
54
+ #
55
+ # p = Password.new( :new, nil, :md5, :length => 10 )
56
+ #
57
+ #
58
+ # === Getting the hash
59
+ #
60
+ # This hash will give the hash in binary format.
61
+ #
62
+ # hash = p.hash
63
+ #
64
+ # This hash will give the hash in hexidecimal format.
65
+ #
66
+ # hash = p.hash( :hex )
67
+ #
68
+ #
69
+ # === Authenticating a password
70
+ #
71
+ # This will authenticate the Password +p+ using String +auth_hash_data+ that
72
+ # was gotten from a database.
73
+ #
74
+ # authenticated = p.authenticate( auth_hash_data ) => true/false
75
+ #
76
+ #
77
+ # === Generating a random password
78
+ #
79
+ # See Password.new for all options. The same options may be passed to
80
+ # the constructor's +generation_options+ parameter with the password set as
81
+ # <tt>:new</tt> to generate a new random password.
82
+ #
83
+ # p = Password.new( :new, nil, :sha256, :length => 8,
84
+ # :no_duplicates => true )
85
+ #
86
+ #
87
+ # === Sanitizing the password from memory
88
+ #
89
+ # This will clear the plain-text password String from memory, destructively.
90
+ #
91
+ # p.sanitize
92
+ #
93
+ #
94
+ # === Changing the +salt+ and +digest+
95
+ #
96
+ # When you change/set the +password+, +salt+, or +digest+, you will automatically regenerate
97
+ # the +hash+ that is stored within the Password instance.
98
+ #
99
+ # p.salt = Salt.new
100
+ # p.digest = :sha1
101
+ #
102
+ class Password
103
+ include EncodingTranslation
104
+ attr_reader :password, :salt, :digest
105
+
106
+ ##
107
+ # Initialize a new Password object
108
+ #
109
+ # When +password+ is a String, it is set as the password.
110
+ #
111
+ # When +password+ is <tt>:new</tt>; a new, random password is generated via
112
+ # <tt>RandomStringGenerator</tt> with the given +generation_options+.
113
+ #
114
+ # +salt+: see Password.salt=
115
+ # +digest+: see Password.digest=
116
+ #
117
+ # Generation Options
118
+ #
119
+ # <tt>:length</tt>:: length of the password, default is 8
120
+ # <tt>:no_duplicates</tt>:: no duplicate characters, default is false
121
+ # <tt>:lower_alphas</tt>:: include lower alpha characters, default is true
122
+ # <tt>:upper_alphas</tt>:: include upper alpha characters, default is true
123
+ # <tt>:numerals</tt>:: include numeral characters, default is true
124
+ # <tt>:symbols</tt>:: include symbol characters, default is false
125
+ # <tt>:single_quotes</tt>:: include single quote characters, default is false
126
+ # <tt>:double_quotes</tt>:: include double quote characters, default is false
127
+ # <tt>:backtick</tt>:: include tick characters, default is false
128
+ # <tt>:special</tt>:: use a special string or array of ranges, overrides all other inclusion options
129
+ #
130
+ def initialize( password, salt = nil, digest = :sha256, generation_options = {} )
131
+ if password == :new
132
+ len = generation_options.delete(:length)
133
+ @password = RandomStringGenerator.new( len || 8, generation_options ).generate
134
+ else
135
+ @password = password
136
+ end
137
+ @salt = salt
138
+ @digest = digest
139
+ rehash
140
+ end
141
+
142
+ ##
143
+ # Set the password (and regenerate the hash)
144
+ #
145
+ def password=( new_password )
146
+ @password = new_password
147
+ rehash
148
+ @password
149
+ end
150
+
151
+ ##
152
+ # Santizes the password from the object and from memory.
153
+ #
154
+ # *WARNING: THIS WILL DESTROY THE _ORIGINAL_ PASSWORD STRING ALSO*
155
+ #
156
+ # Exampe:
157
+ # original_pass = "foobar"
158
+ # p = Password.new(original_pass)
159
+ # p.sanitize
160
+ # puts original_pass => ""
161
+ #
162
+ def sanitize
163
+ return unless @password
164
+ @password.sanitize
165
+ @password = nil
166
+ end
167
+
168
+ ##
169
+ # Set the salt (and regenerate the hash)
170
+ #
171
+ # +new_salt+ may be either a Salt or String instance
172
+ #
173
+ def salt=( new_salt )
174
+ @salt = (new_salt.is_a?(String) ? Salt.new(new_salt) : new_salt)
175
+ rehash
176
+ @salt
177
+ end
178
+
179
+ ##
180
+ # Set the digest (and regenerate the hash)
181
+ #
182
+ # +new_digests+ must be one of: <tt>:md5</tt>, <tt>:sha1</tt>,
183
+ # <tt>:sha256</tt>, <tt>:sha384</tt>, <tt>:sha512</tt>
184
+ #
185
+ def digest=( new_digest )
186
+ @digest = new_digest.to_sym
187
+ rehash
188
+ @digest
189
+ end
190
+
191
+ ##
192
+ # Gets the hash, or generates one if one hasn't already been generated, and
193
+ # stores it for future use.
194
+ #
195
+ # +format+ must be one of: <tt>:binary</tt> (default), <tt>:hex</tt>,
196
+ # <tt>:base64</tt>
197
+ #
198
+ def hash( format = :binary )
199
+ case format.to_sym
200
+ when :binary, :bin
201
+ return @hash
202
+ when :hex, :hexidecimal
203
+ return bin_to_hex(@hash)
204
+ when :base64
205
+ return bin_to_b64(@hash)
206
+ else
207
+ raise ArgumentError, "#{format.to_s} is an invalid format."
208
+ end
209
+ end
210
+
211
+ ##
212
+ # Rehash the password.
213
+ #
214
+ # If you
215
+ #
216
+ def rehash
217
+ raise "Password has not been set or has been sanitized." unless @password
218
+
219
+ # Do the salting
220
+ salted = generate_salted( password, salt )
221
+
222
+ # Do the hashing
223
+ @hash = generate_hash( salted, digest )
224
+ end
225
+
226
+ ##
227
+ # Returns the password string, or an empty string if the password has not been set or has been sanitized
228
+ #
229
+ def to_s
230
+ password || ''
231
+ end
232
+
233
+ ##
234
+ # Attempts to authenticate the given password against the given hash. The
235
+ # salt used to create the given hash MUST match the salt of this Password
236
+ # object. The digest and format of the given hash are automatically
237
+ # detected.
238
+ #
239
+ def authenticate( auth_hash_data )
240
+ f, d = detect_type( auth_hash_data )
241
+
242
+ h = generate_hash( generate_salted(password, salt), d )
243
+
244
+ # convert h to proper format
245
+ case f
246
+ when :hex
247
+ h = bin_to_hex h
248
+ when :base64
249
+ h = bin_to_b64 h
250
+ end
251
+
252
+ auth_hash_data.eql?(h)
253
+ end
254
+
255
+ protected
256
+
257
+ ##
258
+ # Detects the type of the hash +h+
259
+ #
260
+ # Returns 2 variables: format, digest
261
+ #
262
+ # Example:
263
+ #
264
+ # format, digest = detect_type( hash )
265
+ def detect_type( h )
266
+ # detect format
267
+ if h == h.match(/[a-f0-9]*/)[0] # hex
268
+ f = :hex
269
+ elsif h.gsub(/\s+/, '') ==
270
+ h.gsub(/\s+/, '').match(/[A-Za-z0-9\+\/\=]*/)[0] # base64
271
+ f = :base64
272
+ else
273
+ f = :binary
274
+ end
275
+
276
+ # convert to binary
277
+ case f
278
+ when :hex
279
+ h = hex_to_bin h
280
+ when :base64
281
+ h = b64_to_bin h
282
+ end
283
+
284
+ # detect digest
285
+ case h.length
286
+ when 16
287
+ d = :md5
288
+ when 20
289
+ d = :sha1
290
+ when 32
291
+ d = :sha256
292
+ when 48
293
+ d = :sha384
294
+ when 64
295
+ d = :sha512
296
+ else
297
+ raise "Invalid hash, digest is unknown."
298
+ end
299
+
300
+ return f, d
301
+ end
302
+
303
+ ##
304
+ # Generates a hash of the given string +str+ and digest +d+
305
+ #
306
+ def generate_hash( str, d )
307
+ case d.to_sym
308
+ when :sha256
309
+ return Digest::SHA256.digest(str)
310
+ when :sha384
311
+ return Digest::SHA384.digest(str)
312
+ when :sha512
313
+ return Digest::SHA512.digest(str)
314
+ when :sha1
315
+ return Digest::SHA1.digest(str)
316
+ when :md5
317
+ return Digest::MD5.digest(str)
318
+ else
319
+ raise RuntimeError, "#{d.to_s} is an invalid digest."
320
+ end
321
+ end
322
+
323
+ ##
324
+ # Generates the salted string for hashing from password +p+ and salt +s+
325
+ #
326
+ def generate_salted( p, s = nil)
327
+ s.nil? ? p.to_s : s.salt_password(p)
328
+ end
329
+ end