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,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
|