pog19 1.1.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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