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