encrypted_strings 0.2.1 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.rdoc +4 -0
- data/README.rdoc +2 -2
- data/Rakefile +1 -1
- data/lib/encrypted_strings/asymmetric_cipher.rb +162 -164
- data/lib/encrypted_strings/cipher.rb +14 -16
- data/lib/encrypted_strings/extensions/string.rb +186 -188
- data/lib/encrypted_strings/sha_cipher.rb +57 -59
- data/lib/encrypted_strings/symmetric_cipher.rb +91 -93
- data/test/asymmetric_cipher_test.rb +24 -24
- data/test/cipher_test.rb +1 -1
- data/test/sha_cipher_test.rb +11 -11
- data/test/string_test.rb +4 -4
- data/test/symmetric_cipher_test.rb +13 -13
- metadata +2 -2
@@ -1,68 +1,66 @@
|
|
1
1
|
require 'digest/sha1'
|
2
2
|
|
3
|
-
module
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
3
|
+
module EncryptedStrings
|
4
|
+
# Encrypts a string using a Secure Hash Algorithm (SHA), specifically SHA-1.
|
5
|
+
#
|
6
|
+
# == Encrypting
|
7
|
+
#
|
8
|
+
# To encrypt a string using an SHA cipher, the salt used to seed the
|
9
|
+
# algorithm must be specified. You can define the default for this value
|
10
|
+
# like so:
|
11
|
+
#
|
12
|
+
# EncryptedStrings::ShaCipher.default_salt = 'secret'
|
13
|
+
#
|
14
|
+
# If these configuration options are not passed in to #encrypt, then the
|
15
|
+
# default values will be used. You can override the default values like so:
|
16
|
+
#
|
17
|
+
# password = 'shhhh'
|
18
|
+
# password.encrypt(:sha, :salt => 'secret') # => "ae645b35bb5dfea6c9133ac872e6adfa92a3c2bd"
|
19
|
+
#
|
20
|
+
# == Decrypting
|
21
|
+
#
|
22
|
+
# SHA-encrypted strings cannot be decrypted. The only way to determine
|
23
|
+
# whether an unencrypted value is equal to an SHA-encrypted string is to
|
24
|
+
# encrypt the value with the same salt. For example,
|
25
|
+
#
|
26
|
+
# password = 'shhhh'.encrypt(:sha, :salt => 'secret') # => "3b22cbe4acde873c3efc82681096f3ae69aff828"
|
27
|
+
# input = 'shhhh'.encrypt(:sha, :salt => 'secret') # => "3b22cbe4acde873c3efc82681096f3ae69aff828"
|
28
|
+
# password == input # => true
|
29
|
+
class ShaCipher < Cipher
|
30
|
+
class << self
|
31
|
+
# The default salt value to use during encryption
|
32
|
+
attr_accessor :default_salt
|
33
|
+
end
|
34
|
+
|
35
|
+
# Set defaults
|
36
|
+
@default_salt = 'salt'
|
37
|
+
|
38
|
+
# The salt value to use for encryption
|
39
|
+
attr_accessor :salt
|
40
|
+
|
41
|
+
# Creates a new cipher that uses an SHA encryption strategy.
|
26
42
|
#
|
27
|
-
#
|
28
|
-
#
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
# The default salt value to use during encryption
|
33
|
-
attr_accessor :default_salt
|
34
|
-
end
|
35
|
-
|
36
|
-
# Set defaults
|
37
|
-
@default_salt = 'salt'
|
43
|
+
# Configuration options:
|
44
|
+
# * +salt+ - Random bytes used as one of the inputs for generating the encrypted string
|
45
|
+
def initialize(options = {})
|
46
|
+
invalid_options = options.keys - [:salt]
|
47
|
+
raise ArgumentError, "Unknown key(s): #{invalid_options.join(", ")}" unless invalid_options.empty?
|
38
48
|
|
39
|
-
|
40
|
-
attr_accessor :salt
|
49
|
+
options = {:salt => ShaCipher.default_salt}.merge(options)
|
41
50
|
|
42
|
-
|
43
|
-
#
|
44
|
-
# Configuration options:
|
45
|
-
# * +salt+ - Random bytes used as one of the inputs for generating the encrypted string
|
46
|
-
def initialize(options = {})
|
47
|
-
invalid_options = options.keys - [:salt]
|
48
|
-
raise ArgumentError, "Unknown key(s): #{invalid_options.join(", ")}" unless invalid_options.empty?
|
49
|
-
|
50
|
-
options = {:salt => ShaCipher.default_salt}.merge(options)
|
51
|
-
|
52
|
-
self.salt = options[:salt].to_s
|
53
|
-
|
54
|
-
super()
|
55
|
-
end
|
51
|
+
self.salt = options[:salt].to_s
|
56
52
|
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
53
|
+
super()
|
54
|
+
end
|
55
|
+
|
56
|
+
# Decryption is not supported
|
57
|
+
def can_decrypt?
|
58
|
+
false
|
59
|
+
end
|
60
|
+
|
61
|
+
# Returns the encrypted value of the data
|
62
|
+
def encrypt(data)
|
63
|
+
Digest::SHA1.hexdigest(data + salt)
|
66
64
|
end
|
67
65
|
end
|
68
66
|
end
|
@@ -1,102 +1,100 @@
|
|
1
|
-
module
|
2
|
-
|
3
|
-
|
4
|
-
|
1
|
+
module EncryptedStrings
|
2
|
+
# Indicates no password was specified for the symmetric cipher
|
3
|
+
class NoPasswordError < StandardError
|
4
|
+
end
|
5
|
+
|
6
|
+
# Symmetric encryption uses a specific algorithm and password to encrypt
|
7
|
+
# the string. As long as the algorithm and password are known, the string
|
8
|
+
# can be decrypted.
|
9
|
+
#
|
10
|
+
# Source: http://support.microsoft.com/kb/246071
|
11
|
+
#
|
12
|
+
# == Encrypting
|
13
|
+
#
|
14
|
+
# To encrypt a string using a symmetric cipher, the algorithm and password
|
15
|
+
# must be specified. You can define the defaults for these values like so:
|
16
|
+
#
|
17
|
+
# EncryptedStrings::SymmetricCipher.default_algorithm = 'des-ecb'
|
18
|
+
# EncryptedStrings::SymmetricCipher.default_password = 'secret'
|
19
|
+
#
|
20
|
+
# If these configuration options are not passed in to #encrypt, then the
|
21
|
+
# default values will be used. You can override the default values like so:
|
22
|
+
#
|
23
|
+
# password = 'shhhh'
|
24
|
+
# password.encrypt(:symmetric, :algorithm => 'des-ecb', :password => 'secret') # => "S/sEkViX3v4=\n"
|
25
|
+
#
|
26
|
+
# An exception will be raised if no password is specified.
|
27
|
+
#
|
28
|
+
# == Decrypting
|
29
|
+
#
|
30
|
+
# To decrypt a string using an symmetric cipher, the algorithm and password
|
31
|
+
# must be specified. Defaults for these values can be defined as show above.
|
32
|
+
#
|
33
|
+
# If these configuration options are not passed in to #decrypt, then the
|
34
|
+
# default values will be used. You can override the default values like so:
|
35
|
+
#
|
36
|
+
# password = "S/sEkViX3v4=\n"
|
37
|
+
# password.decrypt(:symmetric, :algorithm => 'des-ecb', :password => 'secret') # => "shhhh"
|
38
|
+
#
|
39
|
+
# An exception will be raised if no password is specified.
|
40
|
+
class SymmetricCipher < Cipher
|
41
|
+
class << self
|
42
|
+
# The default algorithm to use for encryption. Default is DES-EDE3-CBC.
|
43
|
+
attr_accessor :default_algorithm
|
44
|
+
|
45
|
+
# The default password to use for generating the key and initialization
|
46
|
+
# vector. Default is nil.
|
47
|
+
attr_accessor :default_password
|
5
48
|
end
|
6
49
|
|
7
|
-
#
|
8
|
-
|
9
|
-
|
10
|
-
#
|
11
|
-
|
12
|
-
|
13
|
-
#
|
14
|
-
#
|
15
|
-
|
16
|
-
|
17
|
-
#
|
18
|
-
# PluginAWeek::EncryptedStrings::SymmetricCipher.default_algorithm = 'des-ecb'
|
19
|
-
# PluginAWeek::EncryptedStrings::SymmetricCipher.default_password = 'secret'
|
20
|
-
#
|
21
|
-
# If these configuration options are not passed in to #encrypt, then the
|
22
|
-
# default values will be used. You can override the default values like so:
|
23
|
-
#
|
24
|
-
# password = 'shhhh'
|
25
|
-
# password.encrypt(:symmetric, :algorithm => 'des-ecb', :password => 'secret') # => "S/sEkViX3v4=\n"
|
26
|
-
#
|
27
|
-
# An exception will be raised if no password is specified.
|
28
|
-
#
|
29
|
-
# == Decrypting
|
30
|
-
#
|
31
|
-
# To decrypt a string using an symmetric cipher, the algorithm and password
|
32
|
-
# must be specified. Defaults for these values can be defined as show above.
|
33
|
-
#
|
34
|
-
# If these configuration options are not passed in to #decrypt, then the
|
35
|
-
# default values will be used. You can override the default values like so:
|
36
|
-
#
|
37
|
-
# password = "S/sEkViX3v4=\n"
|
38
|
-
# password.decrypt(:symmetric, :algorithm => 'des-ecb', :password => 'secret') # => "shhhh"
|
50
|
+
# Set default values
|
51
|
+
@default_algorithm = 'DES-EDE3-CBC'
|
52
|
+
|
53
|
+
# The algorithm to use for encryption/decryption
|
54
|
+
attr_accessor :algorithm
|
55
|
+
|
56
|
+
# The password that generates the key/initialization vector for the
|
57
|
+
# algorithm
|
58
|
+
attr_accessor :password
|
59
|
+
|
60
|
+
# Creates a new cipher that uses a symmetric encryption strategy.
|
39
61
|
#
|
40
|
-
#
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
# The default password to use for generating the key and initialization
|
47
|
-
# vector. Default is nil.
|
48
|
-
attr_accessor :default_password
|
49
|
-
end
|
50
|
-
|
51
|
-
# Set default values
|
52
|
-
@default_algorithm = 'DES-EDE3-CBC'
|
53
|
-
|
54
|
-
# The algorithm to use for encryption/decryption
|
55
|
-
attr_accessor :algorithm
|
62
|
+
# Configuration options:
|
63
|
+
# * +algorithm+ - The algorithm to use for generating the encrypted string
|
64
|
+
# * +password+ - The secret value to use for generating the key/initialization vector for the algorithm
|
65
|
+
def initialize(options = {})
|
66
|
+
invalid_options = options.keys - [:algorithm, :password]
|
67
|
+
raise ArgumentError, "Unknown key(s): #{invalid_options.join(", ")}" unless invalid_options.empty?
|
56
68
|
|
57
|
-
|
58
|
-
|
59
|
-
|
69
|
+
options = {
|
70
|
+
:algorithm => SymmetricCipher.default_algorithm,
|
71
|
+
:password => SymmetricCipher.default_password
|
72
|
+
}.merge(options)
|
60
73
|
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
# * +algorithm+ - The algorithm to use for generating the encrypted string
|
65
|
-
# * +password+ - The secret value to use for generating the key/initialization vector for the algorithm
|
66
|
-
def initialize(options = {})
|
67
|
-
invalid_options = options.keys - [:algorithm, :password]
|
68
|
-
raise ArgumentError, "Unknown key(s): #{invalid_options.join(", ")}" unless invalid_options.empty?
|
69
|
-
|
70
|
-
options = {
|
71
|
-
:algorithm => SymmetricCipher.default_algorithm,
|
72
|
-
:password => SymmetricCipher.default_password
|
73
|
-
}.merge(options)
|
74
|
-
|
75
|
-
self.algorithm = options[:algorithm]
|
76
|
-
self.password = options[:password]
|
77
|
-
raise NoPasswordError if password.nil?
|
78
|
-
|
79
|
-
super()
|
80
|
-
end
|
81
|
-
|
82
|
-
# Decrypts the current string using the current key and algorithm specified
|
83
|
-
def decrypt(data)
|
84
|
-
cipher = build_cipher(:decrypt)
|
85
|
-
cipher.update(Base64.decode64(data)) + cipher.final
|
86
|
-
end
|
74
|
+
self.algorithm = options[:algorithm]
|
75
|
+
self.password = options[:password]
|
76
|
+
raise NoPasswordError if password.nil?
|
87
77
|
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
def build_cipher(type) #:nodoc:
|
96
|
-
cipher = OpenSSL::Cipher.new(algorithm).send(type)
|
97
|
-
cipher.pkcs5_keyivgen(password)
|
98
|
-
cipher
|
99
|
-
end
|
78
|
+
super()
|
79
|
+
end
|
80
|
+
|
81
|
+
# Decrypts the current string using the current key and algorithm specified
|
82
|
+
def decrypt(data)
|
83
|
+
cipher = build_cipher(:decrypt)
|
84
|
+
cipher.update(Base64.decode64(data)) + cipher.final
|
100
85
|
end
|
86
|
+
|
87
|
+
# Encrypts the current string using the current key and algorithm specified
|
88
|
+
def encrypt(data)
|
89
|
+
cipher = build_cipher(:encrypt)
|
90
|
+
Base64.encode64(cipher.update(data) + cipher.final)
|
91
|
+
end
|
92
|
+
|
93
|
+
private
|
94
|
+
def build_cipher(type) #:nodoc:
|
95
|
+
cipher = OpenSSL::Cipher.new(algorithm).send(type)
|
96
|
+
cipher.pkcs5_keyivgen(password)
|
97
|
+
cipher
|
98
|
+
end
|
101
99
|
end
|
102
100
|
end
|
@@ -2,27 +2,27 @@ require File.dirname(__FILE__) + '/test_helper'
|
|
2
2
|
|
3
3
|
class NoPrivateKeyErrorTest < Test::Unit::TestCase
|
4
4
|
def test_should_exist
|
5
|
-
assert_not_nil
|
5
|
+
assert_not_nil EncryptedStrings::NoPrivateKeyError
|
6
6
|
end
|
7
7
|
end
|
8
8
|
|
9
9
|
class NoPublicKeyErrorTest < Test::Unit::TestCase
|
10
10
|
def test_should_exist
|
11
|
-
assert_not_nil
|
11
|
+
assert_not_nil EncryptedStrings::NoPublicKeyError
|
12
12
|
end
|
13
13
|
end
|
14
14
|
|
15
15
|
class AsymmetricCipherByDefaultTest < Test::Unit::TestCase
|
16
16
|
def setup
|
17
|
-
@asymmetric_cipher =
|
17
|
+
@asymmetric_cipher = EncryptedStrings::AsymmetricCipher.new(:public_key_file => File.dirname(__FILE__) + '/keys/public')
|
18
18
|
end
|
19
19
|
|
20
20
|
def test_should_raise_an_exception
|
21
|
-
assert_raise(ArgumentError) {
|
21
|
+
assert_raise(ArgumentError) {EncryptedStrings::AsymmetricCipher.new}
|
22
22
|
end
|
23
23
|
|
24
24
|
def test_should_not_have_a_public_key_file
|
25
|
-
@asymmetric_cipher =
|
25
|
+
@asymmetric_cipher = EncryptedStrings::AsymmetricCipher.new(:private_key_file => File.dirname(__FILE__) + '/keys/private')
|
26
26
|
assert_nil @asymmetric_cipher.public_key_file
|
27
27
|
end
|
28
28
|
|
@@ -41,13 +41,13 @@ end
|
|
41
41
|
|
42
42
|
class AsymmetricCipherWithCustomDefaultsTest < Test::Unit::TestCase
|
43
43
|
def setup
|
44
|
-
@original_default_public_key_file =
|
45
|
-
@original_default_private_key_file =
|
44
|
+
@original_default_public_key_file = EncryptedStrings::AsymmetricCipher.default_public_key_file
|
45
|
+
@original_default_private_key_file = EncryptedStrings::AsymmetricCipher.default_private_key_file
|
46
46
|
|
47
|
-
|
48
|
-
|
47
|
+
EncryptedStrings::AsymmetricCipher.default_public_key_file = File.dirname(__FILE__) + '/keys/public'
|
48
|
+
EncryptedStrings::AsymmetricCipher.default_private_key_file = File.dirname(__FILE__) + '/keys/private'
|
49
49
|
|
50
|
-
@asymmetric_cipher =
|
50
|
+
@asymmetric_cipher = EncryptedStrings::AsymmetricCipher.new
|
51
51
|
end
|
52
52
|
|
53
53
|
def test_should_use_default_public_key_file
|
@@ -67,20 +67,20 @@ class AsymmetricCipherWithCustomDefaultsTest < Test::Unit::TestCase
|
|
67
67
|
end
|
68
68
|
|
69
69
|
def teardown
|
70
|
-
|
71
|
-
|
70
|
+
EncryptedStrings::AsymmetricCipher.default_public_key_file = @original_default_public_key_file
|
71
|
+
EncryptedStrings::AsymmetricCipher.default_private_key_file = @original_default_private_key_file
|
72
72
|
end
|
73
73
|
end
|
74
74
|
|
75
75
|
class AsymmetricCipherWithInvalidOptionsTest < Test::Unit::TestCase
|
76
76
|
def test_should_throw_an_exception
|
77
|
-
assert_raise(ArgumentError) {
|
77
|
+
assert_raise(ArgumentError) {EncryptedStrings::AsymmetricCipher.new(:invalid => true)}
|
78
78
|
end
|
79
79
|
end
|
80
80
|
|
81
81
|
class AsymmetricCipherTest < Test::Unit::TestCase
|
82
82
|
def setup
|
83
|
-
@asymmetric_cipher =
|
83
|
+
@asymmetric_cipher = EncryptedStrings::AsymmetricCipher.new(:public_key_file => File.dirname(__FILE__) + '/keys/public')
|
84
84
|
end
|
85
85
|
|
86
86
|
def test_should_be_able_to_decrypt
|
@@ -90,7 +90,7 @@ end
|
|
90
90
|
|
91
91
|
class AsymmetricCipherWithoutPublicKeyTest < Test::Unit::TestCase
|
92
92
|
def setup
|
93
|
-
@asymmetric_cipher =
|
93
|
+
@asymmetric_cipher = EncryptedStrings::AsymmetricCipher.new(:public_key_file => nil, :private_key_file => File.dirname(__FILE__) + '/keys/private')
|
94
94
|
end
|
95
95
|
|
96
96
|
def test_should_not_be_public
|
@@ -98,13 +98,13 @@ class AsymmetricCipherWithoutPublicKeyTest < Test::Unit::TestCase
|
|
98
98
|
end
|
99
99
|
|
100
100
|
def test_should_not_be_able_to_encrypt
|
101
|
-
assert_raise(
|
101
|
+
assert_raise(EncryptedStrings::NoPublicKeyError) {@asymmetric_cipher.encrypt('test')}
|
102
102
|
end
|
103
103
|
end
|
104
104
|
|
105
105
|
class AsymmetricCipherWithPublicKeyTest < Test::Unit::TestCase
|
106
106
|
def setup
|
107
|
-
@asymmetric_cipher =
|
107
|
+
@asymmetric_cipher = EncryptedStrings::AsymmetricCipher.new(:public_key_file => File.dirname(__FILE__) + '/keys/public')
|
108
108
|
end
|
109
109
|
|
110
110
|
def test_should_be_public
|
@@ -120,13 +120,13 @@ class AsymmetricCipherWithPublicKeyTest < Test::Unit::TestCase
|
|
120
120
|
end
|
121
121
|
|
122
122
|
def test_should_not_be_able_to_decrypt
|
123
|
-
assert_raise(
|
123
|
+
assert_raise(EncryptedStrings::NoPrivateKeyError) {@asymmetric_cipher.decrypt("HbEh0Hwri26S7SWYqO26DBbzfhR1h/0pXYLjSKUpxF5DOaOCtD9oRN748+Na\nrfNaVN5Eg7RUhbRFZE+UnNHo6Q==\n")}
|
124
124
|
end
|
125
125
|
end
|
126
126
|
|
127
127
|
class AsymmetricCipherWithoutPrivateKeyTest < Test::Unit::TestCase
|
128
128
|
def setup
|
129
|
-
@asymmetric_cipher =
|
129
|
+
@asymmetric_cipher = EncryptedStrings::AsymmetricCipher.new(:private_key_file => nil, :public_key_file => File.dirname(__FILE__) + '/keys/public')
|
130
130
|
end
|
131
131
|
|
132
132
|
def test_should_not_be_private
|
@@ -134,13 +134,13 @@ class AsymmetricCipherWithoutPrivateKeyTest < Test::Unit::TestCase
|
|
134
134
|
end
|
135
135
|
|
136
136
|
def test_should_not_be_able_to_decrypt
|
137
|
-
assert_raise(
|
137
|
+
assert_raise(EncryptedStrings::NoPrivateKeyError) {@asymmetric_cipher.decrypt("HbEh0Hwri26S7SWYqO26DBbzfhR1h/0pXYLjSKUpxF5DOaOCtD9oRN748+Na\nrfNaVN5Eg7RUhbRFZE+UnNHo6Q==\n")}
|
138
138
|
end
|
139
139
|
end
|
140
140
|
|
141
141
|
class AsymmetricCipherWithPrivateKeyTest < Test::Unit::TestCase
|
142
142
|
def setup
|
143
|
-
@asymmetric_cipher =
|
143
|
+
@asymmetric_cipher = EncryptedStrings::AsymmetricCipher.new(:private_key_file => File.dirname(__FILE__) + '/keys/private')
|
144
144
|
end
|
145
145
|
|
146
146
|
def test_should_not_be_public
|
@@ -152,7 +152,7 @@ class AsymmetricCipherWithPrivateKeyTest < Test::Unit::TestCase
|
|
152
152
|
end
|
153
153
|
|
154
154
|
def test_not_should_be_able_to_encrypt
|
155
|
-
assert_raise(
|
155
|
+
assert_raise(EncryptedStrings::NoPublicKeyError) {@asymmetric_cipher.encrypt('test')}
|
156
156
|
end
|
157
157
|
|
158
158
|
def test_should_be_able_to_decrypt
|
@@ -162,7 +162,7 @@ end
|
|
162
162
|
|
163
163
|
class AsymmetricCipherWithEncryptedPrivateKeyTest < Test::Unit::TestCase
|
164
164
|
def setup
|
165
|
-
@asymmetric_cipher =
|
165
|
+
@asymmetric_cipher = EncryptedStrings::AsymmetricCipher.new(:private_key_file => File.dirname(__FILE__) + '/keys/encrypted_private', :algorithm => 'DES-EDE3-CBC', :password => 'secret')
|
166
166
|
end
|
167
167
|
|
168
168
|
def test_should_not_be_public
|
@@ -174,7 +174,7 @@ class AsymmetricCipherWithEncryptedPrivateKeyTest < Test::Unit::TestCase
|
|
174
174
|
end
|
175
175
|
|
176
176
|
def test_should_not_be_able_to_encrypt
|
177
|
-
assert_raise(
|
177
|
+
assert_raise(EncryptedStrings::NoPublicKeyError) {@asymmetric_cipher.encrypt('test')}
|
178
178
|
end
|
179
179
|
|
180
180
|
def test_should_be_able_to_decrypt
|