encrypted_strings 0.1.1 → 0.2.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.
@@ -6,17 +6,17 @@ module PluginAWeek #:nodoc:
6
6
  #
7
7
  # == Encrypting
8
8
  #
9
- # To encrypt a string using an SHA algorithm, the salt used to seed the
10
- # encrypting must be specified. You can define the default for this
11
- # value like so:
9
+ # To encrypt a string using an SHA cipher, the salt used to seed the
10
+ # algorithm must be specified. You can define the default for this value
11
+ # like so:
12
12
  #
13
- # PluginAWeek::EncryptedStrings::ShaEncryptor.default_salt = "secret"
13
+ # PluginAWeek::EncryptedStrings::ShaCipher.default_salt = 'secret'
14
14
  #
15
15
  # If these configuration options are not passed in to #encrypt, then the
16
16
  # default values will be used. You can override the default values like so:
17
17
  #
18
- # password = "shhhh"
19
- # password.encrypt(:sha, :salt => "my_salt") # => "ae645b35bb5dfea6c9133ac872e6adfa92a3c2bd"
18
+ # password = 'shhhh'
19
+ # password.encrypt(:sha, :salt => 'secret') # => "ae645b35bb5dfea6c9133ac872e6adfa92a3c2bd"
20
20
  #
21
21
  # == Decrypting
22
22
  #
@@ -24,10 +24,10 @@ module PluginAWeek #:nodoc:
24
24
  # whether an unencrypted value is equal to an SHA-encrypted string is to
25
25
  # encrypt the value with the same salt. For example,
26
26
  #
27
- # password = "shhhh".encrypt(:sha, :salt => "secret") # => "3b22cbe4acde873c3efc82681096f3ae69aff828"
28
- # input = "shhhh".encrypt(:sha, :salt => "secret") # => "3b22cbe4acde873c3efc82681096f3ae69aff828"
27
+ # password = 'shhhh'.encrypt(:sha, :salt => 'secret') # => "3b22cbe4acde873c3efc82681096f3ae69aff828"
28
+ # input = 'shhhh'.encrypt(:sha, :salt => 'secret') # => "3b22cbe4acde873c3efc82681096f3ae69aff828"
29
29
  # password == input # => true
30
- class ShaEncryptor < Encryptor
30
+ class ShaCipher < Cipher
31
31
  class << self
32
32
  # The default salt value to use during encryption
33
33
  attr_accessor :default_salt
@@ -39,13 +39,15 @@ module PluginAWeek #:nodoc:
39
39
  # The salt value to use for encryption
40
40
  attr_accessor :salt
41
41
 
42
+ # Creates a new cipher that uses an SHA encryption strategy.
43
+ #
42
44
  # Configuration options:
43
- # * +salt+ - Salt value to use for encryption
45
+ # * +salt+ - Random bytes used as one of the inputs for generating the encrypted string
44
46
  def initialize(options = {})
45
47
  invalid_options = options.keys - [:salt]
46
48
  raise ArgumentError, "Unknown key(s): #{invalid_options.join(", ")}" unless invalid_options.empty?
47
49
 
48
- options = {:salt => ShaEncryptor.default_salt}.merge(options)
50
+ options = {:salt => self.class.default_salt}.merge(options)
49
51
 
50
52
  self.salt = options[:salt].to_s
51
53
 
@@ -0,0 +1,102 @@
1
+ module PluginAWeek #:nodoc:
2
+ module EncryptedStrings
3
+ # Indicates no password was specified for the symmetric cipher
4
+ class NoPasswordError < StandardError
5
+ end
6
+
7
+ # Symmetric encryption uses a specific algorithm and password to encrypt
8
+ # the string. As long as the algorithm and password are known, the string
9
+ # can be decrypted.
10
+ #
11
+ # Source: http://support.microsoft.com/kb/246071
12
+ #
13
+ # == Encrypting
14
+ #
15
+ # To encrypt a string using a symmetric cipher, the algorithm and password
16
+ # must be specified. You can define the defaults for these values like so:
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"
39
+ #
40
+ # An exception will be raised if no password is specified.
41
+ class SymmetricCipher < Cipher
42
+ class << self
43
+ # The default algorithm to use for encryption. Default is DES-EDE3-CBC.
44
+ attr_accessor :default_algorithm
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
56
+
57
+ # The password that generates the key/initialization vector for the
58
+ # algorithm
59
+ attr_accessor :password
60
+
61
+ # Creates a new cipher that uses a symmetric encryption strategy.
62
+ #
63
+ # Configuration options:
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 => self.class.default_algorithm,
72
+ :password => self.class.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
87
+
88
+ # Encrypts the current string using the current key and algorithm specified
89
+ def encrypt(data)
90
+ cipher = build_cipher(:encrypt)
91
+ Base64.encode64(cipher.update(data) + cipher.final)
92
+ end
93
+
94
+ private
95
+ def build_cipher(type) #:nodoc:
96
+ cipher = OpenSSL::Cipher.new(algorithm).send(type)
97
+ cipher.pkcs5_keyivgen(password)
98
+ cipher
99
+ end
100
+ end
101
+ end
102
+ end
@@ -1,5 +1,5 @@
1
1
  require 'encrypted_strings/extensions/string'
2
- require 'encrypted_strings/encryptor'
3
- require 'encrypted_strings/symmetric_encryptor'
4
- require 'encrypted_strings/asymmetric_encryptor'
5
- require 'encrypted_strings/sha_encryptor'
2
+ require 'encrypted_strings/cipher'
3
+ require 'encrypted_strings/symmetric_cipher'
4
+ require 'encrypted_strings/asymmetric_cipher'
5
+ require 'encrypted_strings/sha_cipher'
@@ -0,0 +1,183 @@
1
+ require File.dirname(__FILE__) + '/test_helper'
2
+
3
+ class NoPrivateKeyErrorTest < Test::Unit::TestCase
4
+ def test_should_exist
5
+ assert_not_nil PluginAWeek::EncryptedStrings::NoPrivateKeyError
6
+ end
7
+ end
8
+
9
+ class NoPublicKeyErrorTest < Test::Unit::TestCase
10
+ def test_should_exist
11
+ assert_not_nil PluginAWeek::EncryptedStrings::NoPublicKeyError
12
+ end
13
+ end
14
+
15
+ class AsymmetricCipherByDefaultTest < Test::Unit::TestCase
16
+ def setup
17
+ @asymmetric_cipher = PluginAWeek::EncryptedStrings::AsymmetricCipher.new(:public_key_file => File.dirname(__FILE__) + '/keys/public')
18
+ end
19
+
20
+ def test_should_raise_an_exception
21
+ assert_raise(ArgumentError) {PluginAWeek::EncryptedStrings::AsymmetricCipher.new}
22
+ end
23
+
24
+ def test_should_not_have_a_public_key_file
25
+ @asymmetric_cipher = PluginAWeek::EncryptedStrings::AsymmetricCipher.new(:private_key_file => File.dirname(__FILE__) + '/keys/private')
26
+ assert_nil @asymmetric_cipher.public_key_file
27
+ end
28
+
29
+ def test_should_not_have_a_private_key_file
30
+ assert_nil @asymmetric_cipher.private_key_file
31
+ end
32
+
33
+ def test_should_not_have_an_algorithm
34
+ assert_nil @asymmetric_cipher.algorithm
35
+ end
36
+
37
+ def test_should_not_have_a_password
38
+ assert_nil @asymmetric_cipher.password
39
+ end
40
+ end
41
+
42
+ class AsymmetricCipherWithCustomDefaultsTest < Test::Unit::TestCase
43
+ def setup
44
+ @original_default_public_key_file = PluginAWeek::EncryptedStrings::AsymmetricCipher.default_public_key_file
45
+ @original_default_private_key_file = PluginAWeek::EncryptedStrings::AsymmetricCipher.default_private_key_file
46
+
47
+ PluginAWeek::EncryptedStrings::AsymmetricCipher.default_public_key_file = File.dirname(__FILE__) + '/keys/public'
48
+ PluginAWeek::EncryptedStrings::AsymmetricCipher.default_private_key_file = File.dirname(__FILE__) + '/keys/private'
49
+
50
+ @asymmetric_cipher = PluginAWeek::EncryptedStrings::AsymmetricCipher.new
51
+ end
52
+
53
+ def test_should_use_default_public_key_file
54
+ assert_equal File.dirname(__FILE__) + '/keys/public', @asymmetric_cipher.public_key_file
55
+ end
56
+
57
+ def test_should_use_default_private_key_file
58
+ assert_equal File.dirname(__FILE__) + '/keys/private', @asymmetric_cipher.private_key_file
59
+ end
60
+
61
+ def test_should_not_have_an_algorithm
62
+ assert_nil @asymmetric_cipher.algorithm
63
+ end
64
+
65
+ def test_should_not_have_a_password
66
+ assert_nil @asymmetric_cipher.password
67
+ end
68
+
69
+ def teardown
70
+ PluginAWeek::EncryptedStrings::AsymmetricCipher.default_public_key_file = @original_default_public_key_file
71
+ PluginAWeek::EncryptedStrings::AsymmetricCipher.default_private_key_file = @original_default_private_key_file
72
+ end
73
+ end
74
+
75
+ class AsymmetricCipherWithInvalidOptionsTest < Test::Unit::TestCase
76
+ def test_should_throw_an_exception
77
+ assert_raise(ArgumentError) {PluginAWeek::EncryptedStrings::AsymmetricCipher.new(:invalid => true)}
78
+ end
79
+ end
80
+
81
+ class AsymmetricCipherTest < Test::Unit::TestCase
82
+ def setup
83
+ @asymmetric_cipher = PluginAWeek::EncryptedStrings::AsymmetricCipher.new(:public_key_file => File.dirname(__FILE__) + '/keys/public')
84
+ end
85
+
86
+ def test_should_be_able_to_decrypt
87
+ assert @asymmetric_cipher.can_decrypt?
88
+ end
89
+ end
90
+
91
+ class AsymmetricCipherWithoutPublicKeyTest < Test::Unit::TestCase
92
+ def setup
93
+ @asymmetric_cipher = PluginAWeek::EncryptedStrings::AsymmetricCipher.new(:public_key_file => nil, :private_key_file => File.dirname(__FILE__) + '/keys/private')
94
+ end
95
+
96
+ def test_should_not_be_public
97
+ assert !@asymmetric_cipher.public?
98
+ end
99
+
100
+ def test_should_not_be_able_to_encrypt
101
+ assert_raise(PluginAWeek::EncryptedStrings::NoPublicKeyError) {@asymmetric_cipher.encrypt('test')}
102
+ end
103
+ end
104
+
105
+ class AsymmetricCipherWithPublicKeyTest < Test::Unit::TestCase
106
+ def setup
107
+ @asymmetric_cipher = PluginAWeek::EncryptedStrings::AsymmetricCipher.new(:public_key_file => File.dirname(__FILE__) + '/keys/public')
108
+ end
109
+
110
+ def test_should_be_public
111
+ assert @asymmetric_cipher.public?
112
+ end
113
+
114
+ def test_should_not_be_private
115
+ assert !@asymmetric_cipher.private?
116
+ end
117
+
118
+ def test_should_be_able_to_encrypt
119
+ assert_equal 90, @asymmetric_cipher.encrypt('test').length
120
+ end
121
+
122
+ def test_should_not_be_able_to_decrypt
123
+ assert_raise(PluginAWeek::EncryptedStrings::NoPrivateKeyError) {@asymmetric_cipher.decrypt("HbEh0Hwri26S7SWYqO26DBbzfhR1h/0pXYLjSKUpxF5DOaOCtD9oRN748+Na\nrfNaVN5Eg7RUhbRFZE+UnNHo6Q==\n")}
124
+ end
125
+ end
126
+
127
+ class AsymmetricCipherWithoutPrivateKeyTest < Test::Unit::TestCase
128
+ def setup
129
+ @asymmetric_cipher = PluginAWeek::EncryptedStrings::AsymmetricCipher.new(:private_key_file => nil, :public_key_file => File.dirname(__FILE__) + '/keys/public')
130
+ end
131
+
132
+ def test_should_not_be_private
133
+ assert !@asymmetric_cipher.private?
134
+ end
135
+
136
+ def test_should_not_be_able_to_decrypt
137
+ assert_raise(PluginAWeek::EncryptedStrings::NoPrivateKeyError) {@asymmetric_cipher.decrypt("HbEh0Hwri26S7SWYqO26DBbzfhR1h/0pXYLjSKUpxF5DOaOCtD9oRN748+Na\nrfNaVN5Eg7RUhbRFZE+UnNHo6Q==\n")}
138
+ end
139
+ end
140
+
141
+ class AsymmetricCipherWithPrivateKeyTest < Test::Unit::TestCase
142
+ def setup
143
+ @asymmetric_cipher = PluginAWeek::EncryptedStrings::AsymmetricCipher.new(:private_key_file => File.dirname(__FILE__) + '/keys/private')
144
+ end
145
+
146
+ def test_should_not_be_public
147
+ assert !@asymmetric_cipher.public?
148
+ end
149
+
150
+ def test_should_be_private
151
+ assert @asymmetric_cipher.private?
152
+ end
153
+
154
+ def test_not_should_be_able_to_encrypt
155
+ assert_raise(PluginAWeek::EncryptedStrings::NoPublicKeyError) {@asymmetric_cipher.encrypt('test')}
156
+ end
157
+
158
+ def test_should_be_able_to_decrypt
159
+ assert_equal 'test', @asymmetric_cipher.decrypt("HbEh0Hwri26S7SWYqO26DBbzfhR1h/0pXYLjSKUpxF5DOaOCtD9oRN748+Na\nrfNaVN5Eg7RUhbRFZE+UnNHo6Q==\n")
160
+ end
161
+ end
162
+
163
+ class AsymmetricCipherWithEncryptedPrivateKeyTest < Test::Unit::TestCase
164
+ def setup
165
+ @asymmetric_cipher = PluginAWeek::EncryptedStrings::AsymmetricCipher.new(:private_key_file => File.dirname(__FILE__) + '/keys/encrypted_private', :algorithm => 'DES-EDE3-CBC', :password => 'secret')
166
+ end
167
+
168
+ def test_should_not_be_public
169
+ assert !@asymmetric_cipher.public?
170
+ end
171
+
172
+ def test_should_be_private
173
+ assert @asymmetric_cipher.private?
174
+ end
175
+
176
+ def test_should_not_be_able_to_encrypt
177
+ assert_raise(PluginAWeek::EncryptedStrings::NoPublicKeyError) {@asymmetric_cipher.encrypt('test')}
178
+ end
179
+
180
+ def test_should_be_able_to_decrypt
181
+ assert_equal 'test', @asymmetric_cipher.decrypt("HbEh0Hwri26S7SWYqO26DBbzfhR1h/0pXYLjSKUpxF5DOaOCtD9oRN748+Na\nrfNaVN5Eg7RUhbRFZE+UnNHo6Q==\n")
182
+ end
183
+ end
@@ -0,0 +1,15 @@
1
+ require File.dirname(__FILE__) + '/test_helper'
2
+
3
+ class CipherByDefaultTest < Test::Unit::TestCase
4
+ def setup
5
+ @cipher = PluginAWeek::EncryptedStrings::Cipher.new
6
+ end
7
+
8
+ def test_should_be_able_to_decrypt_by_default
9
+ assert @cipher.can_decrypt?
10
+ end
11
+
12
+ def test_should_raise_exception_if_decrypt_not_implemented
13
+ assert_raises(NotImplementedError) {@cipher.decrypt('test')}
14
+ end
15
+ end
@@ -1,12 +1,12 @@
1
- OBNa1q8kbx8pyZZjIpr/pZV0oulE2czh5JlPW/13XsDKYjIwiLkCTZI5Vhv5
2
- cCF+wQuNvrbnp/QvJGzc5aJK1GwxqEb9hAwCzxw69DVjIhUowXDk/rYWUaTq
3
- YwpieO1v6gQrOVxerOubR7LT6A4I6k1CTECX6zeJeGgT2jOfXzIii0T1gmYD
4
- tJxLlNclpGB+eS+tuwrfyNmE4CkYa5vjuWdxHAeMRXPlm+o3ErE0QZ36Sn51
5
- TScPDsZHo7Ppit87sPXpubM/5JltN0QwV4Hj9dJoSm0QNWDb3BoyT1uuJ0wm
6
- b/adzINN1oUjdgMB9g3kmkErS3MRJtuPRNuU7/GvAuv78pQ5tAdkvk8gu7Cd
7
- 5cFgnzvE7l96oZB6TXgsnpjVLide61Di+mX8K2dvUdoCNgrQ1iPklTbEfJeH
8
- U/GoUV1ZyVnEfHkeWBEmC8YVBeri+lFL923g7z29luhxiBRkhows8TQsQ4hX
9
- SMlFCKCKWKW2/58aSlvj0INa4NiBwkY987/X7VYvnSm747vOcD8Xmnd0Qwvt
10
- weC2/NK11NFTNBIIodqsbvtlt1Ff0SXrO5PfbDU1yY+xzbx7SAwqhImXwScS
11
- 4b0nFpArdfeVKNp5teSiu050m4B8kb2EjPW7NISui5NXxWtU5Mu41EnenGES
12
- CkHsLI3plvL/
1
+ TGr8Jm/7zh1dfjjoNJtOncvX/BdUL47b8k2rQbMTY7VD6ZpD317NTuVX1hHV
2
+ 54Nm6pnCId5wcga3sI3TqolTxkqKBy7Rb1mPVBeJyd7ZuoD7zp65+ws+o4Lr
3
+ Xn/3WculfuThUnDESqD53vnKPCfRK6hoMDygEV7urmuide5ogpZp52lidUku
4
+ cXOHDqfVETX7NNnHHghg/6qDUX7+0qf+XeKe8uiI0cPoE5YFnOHyF7oOBGtR
5
+ IqJG97q1InCJMeAbMSxcjO71Te51Z098yI+XN4rGmXbmzSrVKHMUk0tdsVxi
6
+ CE5OmnzLeXK1xomxkRmXZzl10WDBn9e4knoLJTlDdNR3fA4gLRfy3r6RBlDl
7
+ j7HI53o5gi9PTshSXwHr6Q8SV9fty2Nz3/yRT/ZPLUJC1GUqErLl2j6zYbVR
8
+ 8YMaLt1Zqi79ycPZxZV4Zh57YE86nUqepS1pzVpcS8dHZMne846lTyaOyxZ8
9
+ dxW+18s7E7KqpHj17QYrF6c7R5ZHgoNAbLFeGSUCXqkW8YdyidLqQTzWN7hF
10
+ roVcZWFe8YfSUKmgndVBOHGHxMGr+OYgVdAStOEwmRHaNGgSBE4FCkKXYJUj
11
+ ec3zNpiOCb8zxfNku5nT5nIHnGqO4JKWDvDGVioV+ffHwXpAw3OFE5n+M/uo
12
+ 4ZZbVSh1qZxd
@@ -0,0 +1,81 @@
1
+ require File.dirname(__FILE__) + '/test_helper'
2
+
3
+ class ShaCipherByDefaulTest < Test::Unit::TestCase
4
+ def setup
5
+ @sha_cipher = PluginAWeek::EncryptedStrings::ShaCipher.new
6
+ end
7
+
8
+ def test_should_use_default_salt
9
+ assert_equal 'salt', @sha_cipher.salt
10
+ end
11
+
12
+ def test_should_encrypt_using_default_salt
13
+ assert_equal 'f438229716cab43569496f3a3630b3727524b81b', @sha_cipher.encrypt('test')
14
+ end
15
+ end
16
+
17
+ class ShaCipherWithCustomDefaultsTest < Test::Unit::TestCase
18
+ def setup
19
+ @original_default_salt = PluginAWeek::EncryptedStrings::ShaCipher.default_salt
20
+ PluginAWeek::EncryptedStrings::ShaCipher.default_salt = 'custom_salt'
21
+ @sha_cipher = PluginAWeek::EncryptedStrings::ShaCipher.new
22
+ end
23
+
24
+ def test_should_use_custom_default_salt
25
+ assert_equal 'custom_salt', @sha_cipher.salt
26
+ end
27
+
28
+ def test_should_encrypt_using_custom_default_salt
29
+ assert_equal '280f3c516070b09aa3eb755378509c725a9c6561', @sha_cipher.encrypt('test')
30
+ end
31
+
32
+ def teardown
33
+ PluginAWeek::EncryptedStrings::ShaCipher.default_salt = @original_default_salt
34
+ end
35
+ end
36
+
37
+ class ShaCipherWithInvalidOptionsTest < Test::Unit::TestCase
38
+ def test_should_throw_an_exception
39
+ assert_raise(ArgumentError) {PluginAWeek::EncryptedStrings::ShaCipher.new(:invalid => true)}
40
+ end
41
+ end
42
+
43
+ class ShaCipherTest < Test::Unit::TestCase
44
+ def setup
45
+ @sha_cipher = PluginAWeek::EncryptedStrings::ShaCipher.new
46
+ end
47
+
48
+ def test_should_not_be_able_to_decrypt
49
+ assert !PluginAWeek::EncryptedStrings::ShaCipher.new.can_decrypt?
50
+ end
51
+
52
+ def test_should_raise_exception_if_trying_to_decrypt
53
+ assert_raises(NotImplementedError) {PluginAWeek::EncryptedStrings::ShaCipher.new.decrypt('test')}
54
+ end
55
+ end
56
+
57
+ class ShaCipherWithCustomOptionsTest < Test::Unit::TestCase
58
+ def setup
59
+ @sha_cipher = PluginAWeek::EncryptedStrings::ShaCipher.new(:salt => 'different salt')
60
+ end
61
+
62
+ def test_should_use_custom_salt
63
+ assert_equal 'different salt', @sha_cipher.salt
64
+ end
65
+
66
+ def test_should_encrypt_using_custom_salt
67
+ assert_equal '18e3256d71529db8fa65b2eef24a69ddad7070f3', @sha_cipher.encrypt('test')
68
+ end
69
+ end
70
+
71
+ class ShaCipherWithNonStringSaltTest < Test::Unit::TestCase
72
+ require 'time'
73
+
74
+ def setup
75
+ @sha_cipher = PluginAWeek::EncryptedStrings::ShaCipher.new(:salt => Time.parse('Tue Jan 01 00:00:00 UTC 2008'))
76
+ end
77
+
78
+ def test_should_stringify_salt
79
+ assert_equal 'Tue Jan 01 00:00:00 UTC 2008', @sha_cipher.salt
80
+ end
81
+ end