dionysus 0.2.1 → 0.3.0
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/Rakefile +1 -1
- data/VERSION +1 -1
- data/dionysus.gemspec +14 -8
- data/lib/dionysus.rb +1 -1
- data/lib/dionysus/digest.rb +105 -0
- data/lib/dionysus/security.rb +2 -0
- data/lib/dionysus/security/password_salt.rb +139 -0
- data/lib/dionysus/security/string.rb +41 -15
- data/lib/dionysus/string.rb +17 -0
- data/spec/digest_spec.rb +125 -0
- data/spec/password_salt_spec.rb +159 -0
- data/spec/string_security_spec.rb +18 -0
- data/spec/string_spec.rb +20 -1
- metadata +15 -10
data/Rakefile
CHANGED
@@ -9,7 +9,7 @@ begin
|
|
9
9
|
gem.email = "warlickt@operissystems.com"
|
10
10
|
gem.homepage = "http://github.com/tekwiz/dionysus"
|
11
11
|
gem.authors = ["Travis D. Warlick, Jr."]
|
12
|
-
gem.add_dependency 'activesupport', '3.0.0
|
12
|
+
gem.add_dependency 'activesupport', '~> 3.0.0'
|
13
13
|
gem.add_development_dependency "rspec", ">= 1.2.9"
|
14
14
|
# gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
|
15
15
|
end
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.3.0
|
data/dionysus.gemspec
CHANGED
@@ -5,11 +5,11 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = %q{dionysus}
|
8
|
-
s.version = "0.
|
8
|
+
s.version = "0.3.0"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
11
|
s.authors = ["Travis D. Warlick, Jr."]
|
12
|
-
s.date = %q{2010-
|
12
|
+
s.date = %q{2010-04-02}
|
13
13
|
s.email = %q{warlickt@operissystems.com}
|
14
14
|
s.extra_rdoc_files = [
|
15
15
|
"LICENSE",
|
@@ -25,11 +25,15 @@ Gem::Specification.new do |s|
|
|
25
25
|
"dionysus.gemspec",
|
26
26
|
"lib/dionysus.rb",
|
27
27
|
"lib/dionysus/configuration.rb",
|
28
|
+
"lib/dionysus/digest.rb",
|
28
29
|
"lib/dionysus/security.rb",
|
30
|
+
"lib/dionysus/security/password_salt.rb",
|
29
31
|
"lib/dionysus/security/string.rb",
|
30
32
|
"lib/dionysus/string.rb",
|
31
33
|
"spec/configuration_spec.rb",
|
34
|
+
"spec/digest_spec.rb",
|
32
35
|
"spec/dionysus_spec.rb",
|
36
|
+
"spec/password_salt_spec.rb",
|
33
37
|
"spec/spec.opts",
|
34
38
|
"spec/spec_helper.rb",
|
35
39
|
"spec/string_security_spec.rb",
|
@@ -41,11 +45,13 @@ Gem::Specification.new do |s|
|
|
41
45
|
s.rubygems_version = %q{1.3.6}
|
42
46
|
s.summary = %q{A helpful set of utility classes, generators, and command-line tools.}
|
43
47
|
s.test_files = [
|
44
|
-
"spec/
|
48
|
+
"spec/configuration_spec.rb",
|
49
|
+
"spec/digest_spec.rb",
|
50
|
+
"spec/dionysus_spec.rb",
|
51
|
+
"spec/password_salt_spec.rb",
|
45
52
|
"spec/spec_helper.rb",
|
46
|
-
"spec/configuration_spec.rb",
|
47
53
|
"spec/string_security_spec.rb",
|
48
|
-
"spec/
|
54
|
+
"spec/string_spec.rb"
|
49
55
|
]
|
50
56
|
|
51
57
|
if s.respond_to? :specification_version then
|
@@ -53,14 +59,14 @@ Gem::Specification.new do |s|
|
|
53
59
|
s.specification_version = 3
|
54
60
|
|
55
61
|
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
56
|
-
s.add_runtime_dependency(%q<activesupport>, ["
|
62
|
+
s.add_runtime_dependency(%q<activesupport>, ["~> 3.0.0"])
|
57
63
|
s.add_development_dependency(%q<rspec>, [">= 1.2.9"])
|
58
64
|
else
|
59
|
-
s.add_dependency(%q<activesupport>, ["
|
65
|
+
s.add_dependency(%q<activesupport>, ["~> 3.0.0"])
|
60
66
|
s.add_dependency(%q<rspec>, [">= 1.2.9"])
|
61
67
|
end
|
62
68
|
else
|
63
|
-
s.add_dependency(%q<activesupport>, ["
|
69
|
+
s.add_dependency(%q<activesupport>, ["~> 3.0.0"])
|
64
70
|
s.add_dependency(%q<rspec>, [">= 1.2.9"])
|
65
71
|
end
|
66
72
|
end
|
data/lib/dionysus.rb
CHANGED
@@ -0,0 +1,105 @@
|
|
1
|
+
require 'dionysus'
|
2
|
+
require 'digest'
|
3
|
+
|
4
|
+
##
|
5
|
+
# Convenience methods for the Digest module.
|
6
|
+
#
|
7
|
+
# require 'dionysus/digest'
|
8
|
+
#
|
9
|
+
# The <tt>Digest::DEFAULT_DIGESTS</tt> are automatically registered, if they
|
10
|
+
# exist. You can register additional digests with Digest.register_digest.
|
11
|
+
# The given class must give the digest with the <tt>digest(string)</tt>.
|
12
|
+
#
|
13
|
+
# TODO add digest detection -- by length and by proc on the digests hash
|
14
|
+
module Digest
|
15
|
+
DEFAULT_DIGESTS = [:md5, :sha1, :sha2, :sha256, :sha384, :sha512]
|
16
|
+
@digests = {}
|
17
|
+
|
18
|
+
##
|
19
|
+
# Register a digest. Raises an error if the interpreted class doesn't exist.
|
20
|
+
# It will interpret the klass as <tt>Digest::SYM</tt> if it's <tt>nil</tt>,
|
21
|
+
# and it will run the <tt>digest</tt> method on the klass to determine the
|
22
|
+
# digests bit length if bits is <tt>nil</tt>.
|
23
|
+
#
|
24
|
+
# This will register <tt>:my_digest</tt> and automatically determine the bit
|
25
|
+
# length by executing the class's <tt>digest</tt> method on the string
|
26
|
+
# <tt>'1'</tt>:
|
27
|
+
#
|
28
|
+
# Digest.register_digest!( :my_digest, :klass => MyDigestClass )
|
29
|
+
#
|
30
|
+
# Options:
|
31
|
+
# [klass] The digest class (also can be an arbitrary object). Default:
|
32
|
+
# <tt>Digest::#{sym.to_s.upcase}</tt>
|
33
|
+
# [bit_length] The bit length of the digest. Default: calculated by
|
34
|
+
# running the digest on the string <tt>'1'</tt>.
|
35
|
+
# [method] The calculation method for the digest. Default:
|
36
|
+
# <tt>:digest</tt>
|
37
|
+
def self.register_digest!( sym, options = {} )
|
38
|
+
options = options.with_indifferent_access
|
39
|
+
options[:method] ||= :digest
|
40
|
+
options[:klass] ||= "Digest::#{sym.to_s.upcase}".constantize
|
41
|
+
options[:bit_length] ||= options[:klass].send(options[:method], '1').length * 8
|
42
|
+
@digests[sym.to_sym] = options
|
43
|
+
end
|
44
|
+
|
45
|
+
##
|
46
|
+
# Register a digest. Returns nil if an error occurs.
|
47
|
+
def self.register_digest( sym, options = {} )
|
48
|
+
self.register_digest!(sym, options)
|
49
|
+
rescue LoadError
|
50
|
+
nil
|
51
|
+
end
|
52
|
+
|
53
|
+
##
|
54
|
+
# The hash of registered digests.
|
55
|
+
def self.digests
|
56
|
+
@digests
|
57
|
+
end
|
58
|
+
|
59
|
+
##
|
60
|
+
# The available digests.
|
61
|
+
def self.available_digests
|
62
|
+
self.digests.keys
|
63
|
+
end
|
64
|
+
|
65
|
+
##
|
66
|
+
# The lengths of the registered digests in the given encoding.
|
67
|
+
def self.digest_lengths( encoding = :binary )
|
68
|
+
if encoding.is_a?(Symbol)
|
69
|
+
bits_per_char = case encoding
|
70
|
+
when :binary then 8
|
71
|
+
when :base64 then 6
|
72
|
+
when :hex, :hexidecimal then 4
|
73
|
+
when :bit then 1
|
74
|
+
else raise ArgumentError, "Invalid encoding"
|
75
|
+
end
|
76
|
+
elsif encoding.is_a?(Integer) and encoding > 0
|
77
|
+
bits_per_char = encoding
|
78
|
+
else
|
79
|
+
raise ArgumentError, "Invalid encoding"
|
80
|
+
end
|
81
|
+
|
82
|
+
Hash[ self.digests.collect { |dig, info| [dig, info[:bit_length] / bits_per_char] } ]
|
83
|
+
end
|
84
|
+
|
85
|
+
##
|
86
|
+
# Calculate the given digest of the given string.
|
87
|
+
#
|
88
|
+
# Examples:
|
89
|
+
#
|
90
|
+
# Digest.digest(:sha512, 'foobar') #=> binary digest
|
91
|
+
# Digest.digest(Digest::SHA512, 'foobar') #=> binary digest
|
92
|
+
def self.digest( sym_or_klass, str )
|
93
|
+
if sym_or_klass.is_a?(Class)
|
94
|
+
sym_or_klass
|
95
|
+
else
|
96
|
+
Digest.const_get(sym_or_klass.to_s.upcase)
|
97
|
+
end.digest(str)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
# Register some default digests
|
102
|
+
Digest::DEFAULT_DIGESTS.each do |dig|
|
103
|
+
Digest.register_digest(dig)
|
104
|
+
end
|
105
|
+
|
data/lib/dionysus/security.rb
CHANGED
@@ -0,0 +1,139 @@
|
|
1
|
+
require 'dionysus'
|
2
|
+
require 'active_support/secure_random'
|
3
|
+
|
4
|
+
##
|
5
|
+
# Encapsulates a password salt.
|
6
|
+
#
|
7
|
+
# Examples
|
8
|
+
#
|
9
|
+
# salt = PasswordSalt.new
|
10
|
+
# #=> generates random salt of 8 characters
|
11
|
+
# salt.salt_password('foobar') #=> 'foobar0PD0oKAj'
|
12
|
+
#
|
13
|
+
# salt = PasswordSalt.new(:length => 20)
|
14
|
+
# #=> generates random salt of 20 characters
|
15
|
+
# salt.salt_password('foobar') #=> 'foobar7qvFpfi+3jGVFaA5TaE7'
|
16
|
+
#
|
17
|
+
# salt = PasswordSalt.new('ABCDEFG', :beginning)
|
18
|
+
# #=> generates salt 'abcdef' with beginning placement
|
19
|
+
# salt.salt_password('foobar')
|
20
|
+
# #=> 'ABCDEFGfoobar'
|
21
|
+
#
|
22
|
+
# salt = PasswordSalt.new(:split, :length => 10)
|
23
|
+
# #=> generates random salt of 10 characters with split placement
|
24
|
+
# salt.salt_password('foobar')
|
25
|
+
# #=> 'WyVjpfoobarjGYXJ'
|
26
|
+
class PasswordSalt
|
27
|
+
PLACEMENTS = [:before, :after, :split]
|
28
|
+
DEFAULT_LENGTH = 8
|
29
|
+
DEFAULT_PLACEMENT = :after
|
30
|
+
|
31
|
+
@@secure_random = SecureRandom
|
32
|
+
cattr_accessor :secure_random
|
33
|
+
|
34
|
+
attr_accessor :string
|
35
|
+
attr_reader :placement
|
36
|
+
|
37
|
+
##
|
38
|
+
# Generate a salt string of the given length (Default: 8) with the base64
|
39
|
+
# character set. Optionally, you may pass the format as <tt>:binary</tt> to generate
|
40
|
+
# a binary salt.
|
41
|
+
def self.generate( length = DEFAULT_LENGTH, format = :base64 )
|
42
|
+
if length < 0
|
43
|
+
raise ArgumentError, "Invalid length: #{length}"
|
44
|
+
end
|
45
|
+
|
46
|
+
case format.to_sym
|
47
|
+
when :base64
|
48
|
+
self.secure_random.base64(length)[0...length]
|
49
|
+
when :binary
|
50
|
+
self.secure_random.random_bytes(length)
|
51
|
+
else
|
52
|
+
raise ArgumentError, "Invalid format: #{format}"
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
##
|
57
|
+
# Initialize a new PasswordSalt
|
58
|
+
#
|
59
|
+
# If you pass the first argument as a string, the string will be set as
|
60
|
+
# the salt:
|
61
|
+
# PasswordSalt.new('ABCDEFG')
|
62
|
+
#
|
63
|
+
# If the first argument is <tt>:new</tt> or is an option hash, a random salt
|
64
|
+
# will be generated:
|
65
|
+
# PasswordSalt.new(:new)
|
66
|
+
# PasswordSalt.new(:length => 20)
|
67
|
+
# PasswordSalt.new
|
68
|
+
#
|
69
|
+
# If the first or second argument is a Symbol (other than <tt>:new</tt>), it
|
70
|
+
# will be interpreted as the placement:
|
71
|
+
# PasswordSalt.new(:before)
|
72
|
+
# PasswordSalt.new(:new, :before)
|
73
|
+
# PasswordSalt.new('ABCDEFG', :before)
|
74
|
+
#
|
75
|
+
# You may always pass in an options hash as the last argument.
|
76
|
+
# [length] Length of salt to be generated.
|
77
|
+
# Default: 8
|
78
|
+
def initialize( *args )
|
79
|
+
options = args.extract_options!
|
80
|
+
if args[0].nil? or args[0] == :new or args[0].is_a?(String)
|
81
|
+
self.string = args[0] || :new
|
82
|
+
end
|
83
|
+
|
84
|
+
if args[0].is_a?(Symbol) and args[0] != :new
|
85
|
+
self.placement = args[0]
|
86
|
+
elsif args[1].is_a?(Symbol)
|
87
|
+
self.placement = args[1]
|
88
|
+
else
|
89
|
+
self.placement = DEFAULT_PLACEMENT
|
90
|
+
end
|
91
|
+
|
92
|
+
if self.string == :new
|
93
|
+
self.string = self.class.generate(options[:length] || DEFAULT_LENGTH)
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
##
|
98
|
+
# Returns the given password, but salted.
|
99
|
+
def salt_password( password )
|
100
|
+
case self.placement
|
101
|
+
when :after
|
102
|
+
password.to_s + string
|
103
|
+
when :before
|
104
|
+
string + password.to_s
|
105
|
+
when :split
|
106
|
+
string[0...(string.length/2).floor] +
|
107
|
+
password.to_s +
|
108
|
+
string[(string.length/2).floor...string.length]
|
109
|
+
else
|
110
|
+
raise "Invalid salt placement: #{self.placement}"
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
##
|
115
|
+
# Set the salt's placement
|
116
|
+
#
|
117
|
+
# [after] Place the salt after the password.
|
118
|
+
# [before] Place the salt before the password.
|
119
|
+
# [split] Place half of the salt before and half after the password. For
|
120
|
+
# salts of odd length, the shorter half will be in front.
|
121
|
+
def placement=( sym )
|
122
|
+
unless PLACEMENTS.include?(sym.to_sym)
|
123
|
+
raise ArgumentError, "Invalid salt placement: #{sym}"
|
124
|
+
end
|
125
|
+
@placement = sym.to_sym
|
126
|
+
end
|
127
|
+
|
128
|
+
##
|
129
|
+
# Returns the salt string.
|
130
|
+
def to_s
|
131
|
+
self.string
|
132
|
+
end
|
133
|
+
|
134
|
+
##
|
135
|
+
# Returns +true+ if the given salt is equivilent to this salt, +false+ otherwise
|
136
|
+
def eql?( salt )
|
137
|
+
self.to_s.eql?(salt.to_s) && self.placement.eql?(salt.placement)
|
138
|
+
end
|
139
|
+
end
|
@@ -1,9 +1,8 @@
|
|
1
|
-
require 'digest/sha1'
|
2
|
-
require 'digest/sha2'
|
3
|
-
require 'digest/md5'
|
4
|
-
|
5
1
|
require 'dionysus'
|
2
|
+
require 'dionysus/digest'
|
6
3
|
require 'dionysus/string'
|
4
|
+
require 'dionysus/security/password_salt'
|
5
|
+
|
7
6
|
##
|
8
7
|
# Adds String hashing and salting convenience methods.
|
9
8
|
#
|
@@ -20,23 +19,32 @@ class String
|
|
20
19
|
##
|
21
20
|
# Generate a random, binary salt with the given length (default 16)
|
22
21
|
def self.salt( length = 16 )
|
23
|
-
length
|
22
|
+
PasswordSalt.generate(length, :binary).to_s
|
23
|
+
end
|
24
|
+
|
25
|
+
##
|
26
|
+
# Sanitize the String from memory. This is non-reversible. Runs 7 passes
|
27
|
+
# by default.
|
28
|
+
#
|
29
|
+
def sanitize( passes = 7 )
|
30
|
+
passes.times do
|
31
|
+
(0...self.length).each { |i| self[i] = rand(256) }
|
32
|
+
end
|
33
|
+
(0...self.length).each { |i| self[i] = 0 }
|
34
|
+
self.delete!("\000")
|
24
35
|
end
|
25
36
|
|
26
37
|
##
|
27
38
|
# Generate the given digest hash.
|
28
39
|
#
|
29
40
|
# Options:
|
30
|
-
# [salt] Salt to append to the string.
|
31
|
-
# Default: ''
|
32
|
-
# [encoding] Encoding for the hash: <tt>:binary</tt>, <tt>:hex</tt>,
|
41
|
+
# [salt] Salt to append to the string. (This may be a String or a
|
42
|
+
# PasswordSalt object) Default: ''
|
43
|
+
# [encoding] Encoding for the hash: <tt>:binary</tt>, <tt>:hex</tt>,
|
44
|
+
# <tt>:hexidecimal</tt>, <tt>:base64</tt>
|
33
45
|
# Default: <tt>:base64</tt>
|
34
46
|
def digest( klass, options = {} )
|
35
|
-
|
36
|
-
klass = Digest.const_get(klass.to_s.upcase)
|
37
|
-
end
|
38
|
-
|
39
|
-
digest = klass.digest(_salted(options[:salt]))
|
47
|
+
digest = Digest.digest(klass, _salted(options[:salt]))
|
40
48
|
_encode(digest, options[:encoding] || :base64)
|
41
49
|
end
|
42
50
|
|
@@ -52,12 +60,30 @@ class String
|
|
52
60
|
super
|
53
61
|
end
|
54
62
|
|
63
|
+
##
|
64
|
+
# Detect the digest of the string
|
65
|
+
def detect_digest( encoding )
|
66
|
+
self.class.available_digests.each do |dig|
|
67
|
+
if ''.digest(dig, :encoding => encoding).length == self.length
|
68
|
+
return dig
|
69
|
+
end
|
70
|
+
end
|
71
|
+
raise "Unknown digest."
|
72
|
+
end
|
73
|
+
|
55
74
|
private
|
56
75
|
|
57
76
|
##
|
58
|
-
# Salt the string by appending it to the end
|
77
|
+
# Salt the string by appending it to the end or utilizing the
|
78
|
+
# PasswordSalt's salt_password method
|
59
79
|
def _salted( salt )
|
60
|
-
self
|
80
|
+
return self if salt.blank?
|
81
|
+
|
82
|
+
unless salt.is_a?(PasswordSalt)
|
83
|
+
salt = PasswordSalt.new(salt)
|
84
|
+
end
|
85
|
+
|
86
|
+
salt.salt_password(self)
|
61
87
|
end
|
62
88
|
|
63
89
|
##
|
data/lib/dionysus/string.rb
CHANGED
@@ -6,6 +6,9 @@ require 'active_support/base64'
|
|
6
6
|
#
|
7
7
|
# require 'dionysus/string'
|
8
8
|
class String
|
9
|
+
HEX_REGEXP = /^[a-f0-9]+$/
|
10
|
+
BASE64_REGEXP = /^[A-Za-z0-9\+\/\=]+$/
|
11
|
+
|
9
12
|
##
|
10
13
|
# Encode the Base64 (without newlines)
|
11
14
|
def encode64s() Base64.encode64s(self); end
|
@@ -27,4 +30,18 @@ class String
|
|
27
30
|
# Decode from hexidecimal
|
28
31
|
def decode_hexidecimal() [self].pack('H*'); end
|
29
32
|
alias_method :decode_hex, :decode_hexidecimal
|
33
|
+
|
34
|
+
##
|
35
|
+
# Detect the encoding of the string
|
36
|
+
def detect_encoding
|
37
|
+
return nil if blank?
|
38
|
+
|
39
|
+
if match(HEX_REGEXP)
|
40
|
+
:hex
|
41
|
+
elsif match(BASE64_REGEXP)
|
42
|
+
:base64
|
43
|
+
else
|
44
|
+
:binary
|
45
|
+
end
|
46
|
+
end
|
30
47
|
end
|
data/spec/digest_spec.rb
ADDED
@@ -0,0 +1,125 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
|
+
|
3
|
+
require 'dionysus/digest'
|
4
|
+
|
5
|
+
describe Digest do
|
6
|
+
it 'should have the default available digests' do
|
7
|
+
Digest.available_digests.length.should == Digest::DEFAULT_DIGESTS.length
|
8
|
+
Digest::DEFAULT_DIGESTS.each do |dig|
|
9
|
+
Digest.available_digests.should include(dig)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
describe 'registration' do
|
14
|
+
before(:all) do
|
15
|
+
Digest.digests.clear
|
16
|
+
end
|
17
|
+
|
18
|
+
after(:each) do
|
19
|
+
Digest.digests.clear
|
20
|
+
end
|
21
|
+
|
22
|
+
after(:all) do
|
23
|
+
Digest::DEFAULT_DIGESTS.each do |sym|
|
24
|
+
Digest.register_digest!(sym)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
it 'should register an available digest' do
|
29
|
+
Digest.register_digest(:md5).should be_a(Hash)
|
30
|
+
Digest.digests[:md5].should be_a(Hash)
|
31
|
+
end
|
32
|
+
|
33
|
+
it 'should register an available digest with bang' do
|
34
|
+
Digest.register_digest!(:md5).should be_a(Hash)
|
35
|
+
Digest.digests[:md5].should be_a(Hash)
|
36
|
+
end
|
37
|
+
|
38
|
+
it 'should automatically interpret the class' do
|
39
|
+
Digest.register_digest(:md5)
|
40
|
+
Digest.digests[:md5][:klass].should == Digest::MD5
|
41
|
+
end
|
42
|
+
|
43
|
+
it 'should register a digest with a class' do
|
44
|
+
Digest.register_digest(:md5, :klass => Digest::SHA256)
|
45
|
+
Digest.digests[:md5][:klass].should == Digest::SHA256
|
46
|
+
Digest.digests[:md5][:bit_length].should == 256
|
47
|
+
end
|
48
|
+
|
49
|
+
it 'should automatically interpret the bit length' do
|
50
|
+
Digest.register_digest(:md5)
|
51
|
+
Digest.digests[:md5][:bit_length].should == 128
|
52
|
+
end
|
53
|
+
|
54
|
+
it 'should register a digest with a class and a bit length' do
|
55
|
+
Digest.register_digest(:md5, :klass => Digest::SHA256, :bit_length => 1)
|
56
|
+
Digest.digests[:md5][:klass].should == Digest::SHA256
|
57
|
+
Digest.digests[:md5][:bit_length].should == 1
|
58
|
+
end
|
59
|
+
|
60
|
+
it 'should quietly reject a digest if it does not exist' do
|
61
|
+
Digest.register_digest(:foobar).should == nil
|
62
|
+
Digest.digests.should == {}
|
63
|
+
end
|
64
|
+
|
65
|
+
it 'should raise an error if a digest does not exist' do
|
66
|
+
lambda { Digest.register_digest!(:foobar) }.should
|
67
|
+
raise_error(LoadError, 'library not found for class Digest::FOOBAR -- digest/foobar')
|
68
|
+
end
|
69
|
+
|
70
|
+
it 'should take an arbitrary object that responds to digest' do
|
71
|
+
my_digest = mock('my_digest')
|
72
|
+
my_digest.should_receive(:digest).with('1').and_return('12345')
|
73
|
+
Digest.register_digest(:my_digest, :klass => my_digest)
|
74
|
+
Digest.digests[:my_digest][:bit_length].should == 5 * 8
|
75
|
+
Digest.digests[:my_digest][:klass].should === my_digest
|
76
|
+
Digest.digests[:my_digest][:method].should == :digest
|
77
|
+
end
|
78
|
+
|
79
|
+
it 'should take an arbitrary method' do
|
80
|
+
my_digest = mock('my_digest')
|
81
|
+
my_digest.should_receive(:do_digest).with('1').and_return('12345')
|
82
|
+
Digest.register_digest(:my_digest, :klass => my_digest, :method => :do_digest)
|
83
|
+
Digest.digests[:my_digest][:bit_length].should == 5 * 8
|
84
|
+
Digest.digests[:my_digest][:klass].should === my_digest
|
85
|
+
Digest.digests[:my_digest][:method].should == :do_digest
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
describe 'digest lengths' do
|
90
|
+
{ :md5 => 128, :sha1 => 160, :sha2 => 256,
|
91
|
+
:sha256 => 256, :sha384 => 384, :sha512 => 512 }.each do |dig, bits|
|
92
|
+
it "should have binary length for #{dig}" do
|
93
|
+
Digest.digest_lengths[dig].should == bits / 8
|
94
|
+
Digest.digest_lengths(:binary)[dig].should == bits / 8
|
95
|
+
Digest.digest_lengths(8)[dig].should == bits / 8
|
96
|
+
end
|
97
|
+
|
98
|
+
it "should have hex length for #{dig}" do
|
99
|
+
Digest.digest_lengths(:hex)[dig].should == bits / 4
|
100
|
+
Digest.digest_lengths(:hexidecimal)[dig].should == bits / 4
|
101
|
+
Digest.digest_lengths(4)[dig].should == bits / 4
|
102
|
+
end
|
103
|
+
|
104
|
+
it "should have base64 length for #{dig}" do
|
105
|
+
Digest.digest_lengths(:base64)[dig].should == bits / 6
|
106
|
+
Digest.digest_lengths(6)[dig].should == bits / 6
|
107
|
+
end
|
108
|
+
|
109
|
+
it "should have bit length for #{dig}" do
|
110
|
+
Digest.digest_lengths(:bit)[dig].should == bits
|
111
|
+
Digest.digest_lengths(1)[dig].should == bits
|
112
|
+
end
|
113
|
+
|
114
|
+
it "should have arbitrary length for #{dig}" do
|
115
|
+
Digest.digest_lengths(7)[dig].should == bits / 7
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
it 'should reject bit length requests for < 0' do
|
120
|
+
lambda { Digest.digest_lengths(0).should raise_error(ArgumentError, 'Invalid encoding') }
|
121
|
+
lambda { Digest.digest_lengths(-1).should raise_error(ArgumentError, 'Invalid encoding') }
|
122
|
+
lambda { Digest.digest_lengths(-100).should raise_error(ArgumentError, 'Invalid encoding') }
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
@@ -0,0 +1,159 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
|
+
|
3
|
+
require 'dionysus/security/password_salt'
|
4
|
+
describe PasswordSalt do
|
5
|
+
it "should not allow placements other than before, after, and split" do
|
6
|
+
salt = PasswordSalt.new('ABCDEFG')
|
7
|
+
[:before, :after, :split].each do |p|
|
8
|
+
salt.placement = p
|
9
|
+
salt.placement.should == p
|
10
|
+
salt.placement = p.to_s
|
11
|
+
salt.placement.should == p
|
12
|
+
end
|
13
|
+
|
14
|
+
lambda { salt.placement = :wowsers }.should raise_error(ArgumentError, "Invalid salt placement: wowsers")
|
15
|
+
end
|
16
|
+
|
17
|
+
it "should should return the salt string with to_s" do
|
18
|
+
salt = PasswordSalt.new('ABCDEFG')
|
19
|
+
salt.to_s.should == 'ABCDEFG'
|
20
|
+
salt = PasswordSalt.new('ABCDEFG', :after)
|
21
|
+
salt.to_s.should == 'ABCDEFG'
|
22
|
+
salt = PasswordSalt.new()
|
23
|
+
salt.to_s.should == salt.string
|
24
|
+
end
|
25
|
+
|
26
|
+
it "should compare equivalance on both string and placement" do
|
27
|
+
salt1 = PasswordSalt.new('ABCDEFG')
|
28
|
+
salt2 = PasswordSalt.new('ABCDEFG')
|
29
|
+
salt1.eql?(salt2).should be_true
|
30
|
+
salt2.eql?(salt1).should be_true
|
31
|
+
|
32
|
+
salt1 = PasswordSalt.new('ABCDEFG', :before)
|
33
|
+
salt2 = PasswordSalt.new('ABCDEFG', :before)
|
34
|
+
salt1.eql?(salt2).should be_true
|
35
|
+
salt2.eql?(salt1).should be_true
|
36
|
+
|
37
|
+
salt1 = PasswordSalt.new('ABCDEFG', :before)
|
38
|
+
salt2 = PasswordSalt.new('ABCDEFG', :split)
|
39
|
+
salt1.eql?(salt2).should_not be_true
|
40
|
+
salt2.eql?(salt1).should_not be_true
|
41
|
+
end
|
42
|
+
|
43
|
+
describe "initializer" do
|
44
|
+
it "should take the first string arg as the literal salt" do
|
45
|
+
salt = PasswordSalt.new('ABCDEFG')
|
46
|
+
salt.string.should == 'ABCDEFG'
|
47
|
+
end
|
48
|
+
|
49
|
+
it "should take the the second symbold arg as the placement" do
|
50
|
+
salt = PasswordSalt.new('ABCDEFG', :before)
|
51
|
+
salt.placement.should == :before
|
52
|
+
end
|
53
|
+
|
54
|
+
it "should generate a new salt of 8 characters and :after placement with no arguments" do
|
55
|
+
s = PasswordSalt.new
|
56
|
+
s.placement.should == :after
|
57
|
+
s.string.length.should == 8
|
58
|
+
end
|
59
|
+
|
60
|
+
it "should take a length option" do
|
61
|
+
s = PasswordSalt.new(:length => 20)
|
62
|
+
s.placement.should == :after
|
63
|
+
s.string.length.should == 20
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
describe "salt generation" do
|
68
|
+
it "should generate 8 characters by default" do
|
69
|
+
salt = PasswordSalt.generate
|
70
|
+
salt.length.should == 8
|
71
|
+
end
|
72
|
+
|
73
|
+
it "should generate n characters when told to" do
|
74
|
+
(0..20).each do |n|
|
75
|
+
PasswordSalt.generate(n).length.should == n
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
it "should raise an error with negative length" do
|
80
|
+
lambda { PasswordSalt.generate(-1) }.should raise_error(ArgumentError, "Invalid length: -1")
|
81
|
+
end
|
82
|
+
|
83
|
+
it "should generate ascii characters only by default" do
|
84
|
+
100.times do
|
85
|
+
salt = PasswordSalt.generate(100)
|
86
|
+
salt.should match(/\A[A-Za-z0-9\+\/]{100}\Z/)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
it "should generate binary characters" do
|
91
|
+
100.times do
|
92
|
+
salt = PasswordSalt.generate(100, :binary)
|
93
|
+
salt.should_not match(/\A[A-Za-z0-9\+\/]{100}\Z/)
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
describe "after placement" do
|
99
|
+
before(:each) do
|
100
|
+
@password = 'foobar'
|
101
|
+
@salt = "ABCDEFG"
|
102
|
+
end
|
103
|
+
|
104
|
+
it "should put the salt after the password by default" do
|
105
|
+
salt = PasswordSalt.new(@salt)
|
106
|
+
salt.salt_password(@password).should == @password+@salt
|
107
|
+
end
|
108
|
+
|
109
|
+
it "should put the salt after the password" do
|
110
|
+
salt = PasswordSalt.new(@salt, :after)
|
111
|
+
salt.salt_password(@password).should == @password+@salt
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
describe "before placement" do
|
116
|
+
before(:each) do
|
117
|
+
@password = 'foobar'
|
118
|
+
@salt = "ABCDEFG"
|
119
|
+
end
|
120
|
+
|
121
|
+
it "should put the salt before the password" do
|
122
|
+
salt = PasswordSalt.new(@salt, :before)
|
123
|
+
salt.salt_password(@password).should == @salt+@password
|
124
|
+
salt = PasswordSalt.new(@salt)
|
125
|
+
salt.placement = :before
|
126
|
+
salt.salt_password(@password).should == @salt+@password
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
describe "split placement with even number of characters" do
|
131
|
+
before(:each) do
|
132
|
+
@password = 'foobar'
|
133
|
+
@salt = "ABCDEFGH"
|
134
|
+
end
|
135
|
+
|
136
|
+
it "should put half the salt before and half the salt after the password" do
|
137
|
+
salt = PasswordSalt.new(@salt, :split)
|
138
|
+
salt.salt_password(@password).should == 'ABCD'+@password+'EFGH'
|
139
|
+
salt = PasswordSalt.new(@salt)
|
140
|
+
salt.placement = :split
|
141
|
+
salt.salt_password(@password).should == 'ABCD'+@password+'EFGH'
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
describe "split placement with odd number of characters" do
|
146
|
+
before(:each) do
|
147
|
+
@password = 'foobar'
|
148
|
+
@salt = "ABCDEFG"
|
149
|
+
end
|
150
|
+
|
151
|
+
it "should put half the salt before and half the salt after the password" do
|
152
|
+
salt = PasswordSalt.new(@salt, :split)
|
153
|
+
salt.salt_password(@password).should == 'ABC'+@password+'DEFG'
|
154
|
+
salt = PasswordSalt.new(@salt)
|
155
|
+
salt.placement = :split
|
156
|
+
salt.salt_password(@password).should == 'ABC'+@password+'DEFG'
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
@@ -11,6 +11,24 @@ describe String do
|
|
11
11
|
String.salt(1).length.should == 1
|
12
12
|
end
|
13
13
|
|
14
|
+
it "should sanitize the string" do
|
15
|
+
str = 'abcdefg'
|
16
|
+
str.sanitize
|
17
|
+
str.should == ''
|
18
|
+
end
|
19
|
+
|
20
|
+
it "should sanitize the string with 0 passes" do
|
21
|
+
str = 'abcdefg'
|
22
|
+
str.sanitize(0)
|
23
|
+
str.should == ''
|
24
|
+
end
|
25
|
+
|
26
|
+
it "should sanitize the string with 100 passes" do
|
27
|
+
str = 'abcdefg'
|
28
|
+
str.sanitize(100)
|
29
|
+
str.should == ''
|
30
|
+
end
|
31
|
+
|
14
32
|
it "should calculate sha512 in base64" do
|
15
33
|
digest = "KHErTuI/5urCF3vi6yTDxvAHBydW1CN3zVfBWDP/ItbmgeVi5IFq47ew0hPHzyK3AuMDh+Z9MBBlf3/mACgVMQ=="
|
16
34
|
"abcde\240z405".digest(:sha512).should == digest
|
data/spec/string_spec.rb
CHANGED
@@ -24,4 +24,23 @@ describe String do
|
|
24
24
|
'6162636465a07a343035'.decode_hex.should == "abcde\240z405"
|
25
25
|
'6162636465a07a343035'.decode_hexidecimal.should == "abcde\240z405"
|
26
26
|
end
|
27
|
-
|
27
|
+
|
28
|
+
describe 'encoding detection' do
|
29
|
+
it 'should return nil for blank' do
|
30
|
+
''.detect_encoding.should be_nil
|
31
|
+
end
|
32
|
+
|
33
|
+
it 'should match hex' do
|
34
|
+
'6162636465a07a343035'.detect_encoding.should == :hex
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'should match base64' do
|
38
|
+
"YWJjZGWgejQwNQ==".detect_encoding.should == :base64
|
39
|
+
"YWJjZGWgejQwNQ==\n".detect_encoding.should == :base64
|
40
|
+
end
|
41
|
+
|
42
|
+
it 'should default to binary' do
|
43
|
+
"abcde\240z405".detect_encoding.should == :binary
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
metadata
CHANGED
@@ -4,9 +4,9 @@ version: !ruby/object:Gem::Version
|
|
4
4
|
prerelease: false
|
5
5
|
segments:
|
6
6
|
- 0
|
7
|
-
-
|
8
|
-
-
|
9
|
-
version: 0.
|
7
|
+
- 3
|
8
|
+
- 0
|
9
|
+
version: 0.3.0
|
10
10
|
platform: ruby
|
11
11
|
authors:
|
12
12
|
- Travis D. Warlick, Jr.
|
@@ -14,7 +14,7 @@ autorequire:
|
|
14
14
|
bindir: bin
|
15
15
|
cert_chain: []
|
16
16
|
|
17
|
-
date: 2010-
|
17
|
+
date: 2010-04-02 00:00:00 -05:00
|
18
18
|
default_executable:
|
19
19
|
dependencies:
|
20
20
|
- !ruby/object:Gem::Dependency
|
@@ -22,14 +22,13 @@ dependencies:
|
|
22
22
|
prerelease: false
|
23
23
|
requirement: &id001 !ruby/object:Gem::Requirement
|
24
24
|
requirements:
|
25
|
-
- -
|
25
|
+
- - ~>
|
26
26
|
- !ruby/object:Gem::Version
|
27
27
|
segments:
|
28
28
|
- 3
|
29
29
|
- 0
|
30
30
|
- 0
|
31
|
-
|
32
|
-
version: 3.0.0.beta
|
31
|
+
version: 3.0.0
|
33
32
|
type: :runtime
|
34
33
|
version_requirements: *id001
|
35
34
|
- !ruby/object:Gem::Dependency
|
@@ -65,11 +64,15 @@ files:
|
|
65
64
|
- dionysus.gemspec
|
66
65
|
- lib/dionysus.rb
|
67
66
|
- lib/dionysus/configuration.rb
|
67
|
+
- lib/dionysus/digest.rb
|
68
68
|
- lib/dionysus/security.rb
|
69
|
+
- lib/dionysus/security/password_salt.rb
|
69
70
|
- lib/dionysus/security/string.rb
|
70
71
|
- lib/dionysus/string.rb
|
71
72
|
- spec/configuration_spec.rb
|
73
|
+
- spec/digest_spec.rb
|
72
74
|
- spec/dionysus_spec.rb
|
75
|
+
- spec/password_salt_spec.rb
|
73
76
|
- spec/spec.opts
|
74
77
|
- spec/spec_helper.rb
|
75
78
|
- spec/string_security_spec.rb
|
@@ -105,8 +108,10 @@ signing_key:
|
|
105
108
|
specification_version: 3
|
106
109
|
summary: A helpful set of utility classes, generators, and command-line tools.
|
107
110
|
test_files:
|
108
|
-
- spec/string_spec.rb
|
109
|
-
- spec/spec_helper.rb
|
110
111
|
- spec/configuration_spec.rb
|
111
|
-
- spec/
|
112
|
+
- spec/digest_spec.rb
|
112
113
|
- spec/dionysus_spec.rb
|
114
|
+
- spec/password_salt_spec.rb
|
115
|
+
- spec/spec_helper.rb
|
116
|
+
- spec/string_security_spec.rb
|
117
|
+
- spec/string_spec.rb
|