symmetric-encryption 4.0.1 → 4.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +0 -7
- data/lib/symmetric_encryption/cipher.rb +11 -4
- data/lib/symmetric_encryption/cli.rb +39 -28
- data/lib/symmetric_encryption/config.rb +9 -6
- data/lib/symmetric_encryption/encoder.rb +6 -0
- data/lib/symmetric_encryption/extensions/mongoid/encrypted.rb +1 -0
- data/lib/symmetric_encryption/generator.rb +1 -1
- data/lib/symmetric_encryption/header.rb +7 -5
- data/lib/symmetric_encryption/key.rb +2 -62
- data/lib/symmetric_encryption/keystore/aws.rb +172 -0
- data/lib/symmetric_encryption/keystore/environment.rb +7 -30
- data/lib/symmetric_encryption/keystore/file.rb +8 -30
- data/lib/symmetric_encryption/keystore/heroku.rb +22 -0
- data/lib/symmetric_encryption/keystore/memory.rb +4 -3
- data/lib/symmetric_encryption/keystore.rb +151 -36
- data/lib/symmetric_encryption/railtie.rb +9 -4
- data/lib/symmetric_encryption/railties/symmetric_encryption_validator.rb +1 -0
- data/lib/symmetric_encryption/reader.rb +50 -58
- data/lib/symmetric_encryption/symmetric_encryption.rb +2 -1
- data/lib/symmetric_encryption/utils/aws.rb +141 -0
- data/lib/symmetric_encryption/utils/re_encrypt_files.rb +12 -5
- data/lib/symmetric_encryption/version.rb +1 -1
- data/lib/symmetric_encryption/writer.rb +33 -27
- data/lib/symmetric_encryption.rb +27 -6
- data/test/active_record_test.rb +25 -25
- data/test/cipher_test.rb +3 -3
- data/test/header_test.rb +1 -1
- data/test/key_test.rb +0 -157
- data/test/keystore/aws_test.rb +133 -0
- data/test/keystore/environment_test.rb +3 -51
- data/test/keystore/file_test.rb +13 -52
- data/test/keystore/heroku_test.rb +70 -0
- data/test/keystore_test.rb +200 -4
- data/test/mongoid_test.rb +15 -15
- data/test/reader_test.rb +28 -8
- data/test/symmetric_encryption_test.rb +2 -2
- data/test/test_db.sqlite3 +0 -0
- data/test/test_helper.rb +1 -0
- data/test/utils/aws_test.rb +74 -0
- data/test/writer_test.rb +48 -46
- metadata +29 -20
@@ -4,41 +4,20 @@ module SymmetricEncryption
|
|
4
4
|
class Environment < Memory
|
5
5
|
attr_accessor :key_env_var, :encoding
|
6
6
|
|
7
|
-
# Returns [Hash]
|
8
|
-
# Displays the keys that need to be added to the heroku environment.
|
9
|
-
def self.new_config(app_name: 'symmetric-encryption',
|
10
|
-
environments: %i[development test release production],
|
11
|
-
cipher_name: 'aes-256-cbc')
|
12
|
-
|
13
|
-
configs = {}
|
14
|
-
environments.each do |environment|
|
15
|
-
environment = environment.to_sym
|
16
|
-
configs[environment] =
|
17
|
-
if %i[development test].include?(environment)
|
18
|
-
Keystore.dev_config
|
19
|
-
else
|
20
|
-
cfg = new_key_config(cipher_name: cipher_name, app_name: app_name, environment: environment)
|
21
|
-
{
|
22
|
-
ciphers: [cfg]
|
23
|
-
}
|
24
|
-
end
|
25
|
-
end
|
26
|
-
configs
|
27
|
-
end
|
28
|
-
|
29
|
-
# Returns [Hash] a new cipher, and writes its encrypted key file.
|
7
|
+
# Returns [Hash] a new keystore configuration after generating the data key.
|
30
8
|
#
|
31
9
|
# Increments the supplied version number by 1.
|
32
|
-
def self.
|
10
|
+
def self.generate_data_key(cipher_name:, app_name:, environment:, version: 0, dek: nil)
|
33
11
|
version >= 255 ? (version = 1) : (version += 1)
|
34
12
|
|
35
|
-
kek
|
13
|
+
kek = SymmetricEncryption::Key.new(cipher_name: cipher_name)
|
36
14
|
dek ||= SymmetricEncryption::Key.new(cipher_name: cipher_name)
|
37
15
|
|
38
16
|
key_env_var = "#{app_name}_#{environment}_v#{version}".upcase.tr('-', '_')
|
39
17
|
new(key_env_var: key_env_var, key_encrypting_key: kek).write(dek.key)
|
40
18
|
|
41
19
|
{
|
20
|
+
keystore: :environment,
|
42
21
|
cipher_name: dek.cipher_name,
|
43
22
|
version: version,
|
44
23
|
key_env_var: key_env_var,
|
@@ -62,6 +41,7 @@ module SymmetricEncryption
|
|
62
41
|
def read
|
63
42
|
encrypted = ENV[key_env_var]
|
64
43
|
raise "The Environment Variable #{key_env_var} must be set with the encrypted encryption key." unless encrypted
|
44
|
+
|
65
45
|
binary = encoder.decode(encrypted)
|
66
46
|
key_encrypting_key.decrypt(binary)
|
67
47
|
end
|
@@ -70,11 +50,8 @@ module SymmetricEncryption
|
|
70
50
|
def write(key)
|
71
51
|
encrypted_key = key_encrypting_key.encrypt(key)
|
72
52
|
puts "\n\n********************************************************************************"
|
73
|
-
puts
|
74
|
-
puts "
|
75
|
-
puts
|
76
|
-
puts "Or, if using environment variables on another system set the environment variable as follows:\n\n"
|
77
|
-
puts " export #{key_env_var}=\"#{encoder.encode(encrypted_key)}\"\n\n"
|
53
|
+
puts 'Set the environment variable as follows:'
|
54
|
+
puts " export #{key_env_var}=\"#{encoder.encode(encrypted_key)}\""
|
78
55
|
puts '********************************************************************************'
|
79
56
|
end
|
80
57
|
|
@@ -3,46 +3,24 @@ module SymmetricEncryption
|
|
3
3
|
class File
|
4
4
|
attr_accessor :file_name, :key_encrypting_key
|
5
5
|
|
6
|
-
# Returns [Hash]
|
7
|
-
# Generates the encrypted key file for every environment except development and test.
|
8
|
-
def self.new_config(key_path: '/etc/symmetric-encryption',
|
9
|
-
app_name: 'symmetric-encryption',
|
10
|
-
environments: %i[development test release production],
|
11
|
-
cipher_name: 'aes-256-cbc')
|
12
|
-
|
13
|
-
configs = {}
|
14
|
-
environments.each do |environment|
|
15
|
-
environment = environment.to_sym
|
16
|
-
configs[environment] =
|
17
|
-
if %i[development test].include?(environment)
|
18
|
-
Keystore.dev_config
|
19
|
-
else
|
20
|
-
cfg = new_key_config(key_path: key_path, cipher_name: cipher_name, app_name: app_name, environment: environment)
|
21
|
-
{
|
22
|
-
ciphers: [cfg]
|
23
|
-
}
|
24
|
-
end
|
25
|
-
end
|
26
|
-
configs
|
27
|
-
end
|
28
|
-
|
29
|
-
# Returns [Hash] a new cipher, and writes its encrypted key file.
|
6
|
+
# Returns [Hash] a new keystore configuration after generating the data key.
|
30
7
|
#
|
31
8
|
# Increments the supplied version number by 1.
|
32
|
-
def self.
|
9
|
+
def self.generate_data_key(key_path:, cipher_name:, app_name:, environment:, version: 0, dek: nil)
|
33
10
|
version >= 255 ? (version = 1) : (version += 1)
|
34
11
|
|
35
|
-
dek
|
12
|
+
dek ||= SymmetricEncryption::Key.new(cipher_name: cipher_name)
|
36
13
|
kek = SymmetricEncryption::Key.new(cipher_name: cipher_name)
|
37
14
|
kekek = SymmetricEncryption::Key.new(cipher_name: cipher_name)
|
38
15
|
|
39
16
|
dek_file_name = ::File.join(key_path, "#{app_name}_#{environment}_v#{version}.encrypted_key")
|
40
|
-
new(
|
17
|
+
new(key_filename: dek_file_name, key_encrypting_key: kek).write(dek.key)
|
41
18
|
|
42
19
|
kekek_file_name = ::File.join(key_path, "#{app_name}_#{environment}_v#{version}.kekek")
|
43
|
-
new(
|
20
|
+
new(key_filename: kekek_file_name).write(kekek.key)
|
44
21
|
|
45
22
|
{
|
23
|
+
keystore: :file,
|
46
24
|
cipher_name: dek.cipher_name,
|
47
25
|
version: version,
|
48
26
|
key_filename: dek_file_name,
|
@@ -60,8 +38,8 @@ module SymmetricEncryption
|
|
60
38
|
|
61
39
|
# Stores the Encryption key in a file.
|
62
40
|
# Secures the Encryption key by encrypting it with a key encryption key.
|
63
|
-
def initialize(
|
64
|
-
@file_name =
|
41
|
+
def initialize(key_filename:, key_encrypting_key: nil)
|
42
|
+
@file_name = key_filename
|
65
43
|
@key_encrypting_key = key_encrypting_key
|
66
44
|
end
|
67
45
|
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module SymmetricEncryption
|
2
|
+
module Keystore
|
3
|
+
# Heroku uses environment variables too.
|
4
|
+
class Heroku < Environment
|
5
|
+
# Returns [Hash] a new keystore configuration after generating the data key.
|
6
|
+
def self.generate_data_key(**args)
|
7
|
+
config = super(**args)
|
8
|
+
config[:keystore] = :heroku
|
9
|
+
config
|
10
|
+
end
|
11
|
+
|
12
|
+
# Write the encrypted Encryption key to `encrypted_key` attribute.
|
13
|
+
def write(key)
|
14
|
+
encrypted_key = key_encrypting_key.encrypt(key)
|
15
|
+
puts "\n\n********************************************************************************"
|
16
|
+
puts "Add the environment key to Heroku:\n\n"
|
17
|
+
puts " heroku config:add #{key_env_var}=#{encoder.encode(encrypted_key)}"
|
18
|
+
puts '********************************************************************************'
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -5,22 +5,23 @@ module SymmetricEncryption
|
|
5
5
|
attr_accessor :key_encrypting_key
|
6
6
|
attr_reader :encrypted_key
|
7
7
|
|
8
|
-
# Returns [Hash] a new
|
8
|
+
# Returns [Hash] a new keystore configuration after generating the data key.
|
9
9
|
#
|
10
10
|
# Increments the supplied version number by 1.
|
11
11
|
#
|
12
12
|
# Notes:
|
13
13
|
# * For development and testing purposes only!!
|
14
14
|
# * Never store the encrypted encryption key in the source code / config file.
|
15
|
-
def self.
|
15
|
+
def self.generate_data_key(cipher_name:, app_name:, environment:, version: 0, dek: nil)
|
16
16
|
version >= 255 ? (version = 1) : (version += 1)
|
17
17
|
|
18
|
-
kek
|
18
|
+
kek = SymmetricEncryption::Key.new(cipher_name: cipher_name)
|
19
19
|
dek ||= SymmetricEncryption::Key.new(cipher_name: cipher_name)
|
20
20
|
|
21
21
|
encrypted_key = new(key_encrypting_key: kek).write(dek.key)
|
22
22
|
|
23
23
|
{
|
24
|
+
keystore: :memory,
|
24
25
|
cipher_name: cipher_name,
|
25
26
|
version: version,
|
26
27
|
encrypted_key: encrypted_key,
|
@@ -2,11 +2,33 @@ module SymmetricEncryption
|
|
2
2
|
# Encryption keys are secured in Keystores
|
3
3
|
module Keystore
|
4
4
|
# @formatter:off
|
5
|
+
autoload :Aws, 'symmetric_encryption/keystore/aws'
|
5
6
|
autoload :Environment, 'symmetric_encryption/keystore/environment'
|
6
7
|
autoload :File, 'symmetric_encryption/keystore/file'
|
8
|
+
autoload :Heroku, 'symmetric_encryption/keystore/heroku'
|
7
9
|
autoload :Memory, 'symmetric_encryption/keystore/memory'
|
8
10
|
# @formatter:on
|
9
11
|
|
12
|
+
# Returns [Hash] a new keystore configuration after generating data keys for each environment.
|
13
|
+
def self.generate_data_keys(keystore:, environments: %i[development test release production], **args)
|
14
|
+
keystore_class = keystore.is_a?(Symbol) || keystore.is_a?(String) ? constantize_symbol(keystore) : keystore
|
15
|
+
|
16
|
+
configs = {}
|
17
|
+
environments.each do |environment|
|
18
|
+
environment = environment.to_sym
|
19
|
+
configs[environment] =
|
20
|
+
if %i[development test].include?(environment)
|
21
|
+
dev_config
|
22
|
+
else
|
23
|
+
cfg = keystore_class.generate_data_key(environment: environment, **args)
|
24
|
+
{
|
25
|
+
ciphers: [cfg]
|
26
|
+
}
|
27
|
+
end
|
28
|
+
end
|
29
|
+
configs
|
30
|
+
end
|
31
|
+
|
10
32
|
# Returns [Hash] a new configuration file after performing key rotation.
|
11
33
|
#
|
12
34
|
# Perform key rotation for each of the environments in the configuration file, by
|
@@ -27,10 +49,13 @@ module SymmetricEncryption
|
|
27
49
|
# by the servers that have not been updated yet.
|
28
50
|
# Default: false
|
29
51
|
#
|
52
|
+
# keystore: [Symbol]
|
53
|
+
# If supplied, changes the keystore during key rotation.
|
54
|
+
#
|
30
55
|
# Notes:
|
31
56
|
# * iv_filename is no longer supported and is removed when creating a new random cipher.
|
32
57
|
# * `iv` does not need to be encrypted and is included in the clear.
|
33
|
-
def self.rotate_keys!(full_config, environments: [], app_name:, rolling_deploy: false)
|
58
|
+
def self.rotate_keys!(full_config, environments: [], app_name:, rolling_deploy: false, keystore: nil)
|
34
59
|
full_config.each_pair do |environment, cfg|
|
35
60
|
# Only rotate keys for specified environments. Default, all
|
36
61
|
next if !environments.empty? && !environments.include?(environment.to_sym)
|
@@ -43,22 +68,24 @@ module SymmetricEncryption
|
|
43
68
|
# Only generate new keys for keystore's that have a key encrypting key
|
44
69
|
next unless config[:key_encrypting_key] || config[:private_rsa_key]
|
45
70
|
|
46
|
-
cipher_name
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
71
|
+
cipher_name = config[:cipher_name] || 'aes-256-cbc'
|
72
|
+
|
73
|
+
keystore_class = keystore ? constantize_symbol(keystore) : keystore_for(config)
|
74
|
+
|
75
|
+
args = {
|
76
|
+
cipher_name: cipher_name,
|
77
|
+
app_name: app_name,
|
78
|
+
version: version,
|
79
|
+
environment: environment
|
80
|
+
}
|
81
|
+
args[:key_path] = ::File.dirname(config[:key_filename]) if config.key?(:key_filename)
|
82
|
+
new_data_key = keystore_class.generate_data_key(args)
|
56
83
|
|
57
84
|
# Add as second key so that key can be published now and only used in a later deploy.
|
58
85
|
if rolling_deploy
|
59
|
-
cfg[:ciphers].insert(1,
|
86
|
+
cfg[:ciphers].insert(1, new_data_key)
|
60
87
|
else
|
61
|
-
cfg[:ciphers].unshift(
|
88
|
+
cfg[:ciphers].unshift(new_data_key)
|
62
89
|
end
|
63
90
|
end
|
64
91
|
full_config
|
@@ -77,33 +104,35 @@ module SymmetricEncryption
|
|
77
104
|
# Only generate new keys for keystore's that have a key encrypting key
|
78
105
|
next unless config[:key_encrypting_key]
|
79
106
|
|
80
|
-
version
|
107
|
+
version = config.delete(:version) || 1
|
81
108
|
version -= 1
|
82
109
|
|
83
110
|
always_add_header = config.delete(:always_add_header)
|
84
111
|
encoding = config.delete(:encoding)
|
85
112
|
|
86
|
-
|
113
|
+
migrate_config!(config)
|
87
114
|
|
88
115
|
# The current data encrypting key without any of the key encrypting keys.
|
89
|
-
key =
|
116
|
+
key = Keystore.read_key(config)
|
90
117
|
cipher_name = key.cipher_name
|
91
|
-
|
92
|
-
if config.key?(:key_filename)
|
93
|
-
key_path = ::File.dirname(config[:key_filename])
|
94
|
-
Keystore::File.new_key_config(key_path: key_path, cipher_name: cipher_name, app_name: app_name, version: version, environment: environment, dek: key)
|
95
|
-
elsif config.key?(:key_env_var)
|
96
|
-
Keystore::Environment.new_key_config(cipher_name: cipher_name, app_name: app_name, version: version, environment: environment, dek: key)
|
97
|
-
elsif config.key?(:encrypted_key)
|
98
|
-
Keystore::Memory.new_key_config(cipher_name: cipher_name, app_name: app_name, version: version, environment: environment, dek: key)
|
99
|
-
end
|
118
|
+
keystore_class = keystore_for(config)
|
100
119
|
|
101
|
-
|
102
|
-
|
120
|
+
args = {
|
121
|
+
cipher_name: cipher_name,
|
122
|
+
app_name: app_name,
|
123
|
+
version: version,
|
124
|
+
environment: environment,
|
125
|
+
dek: key
|
126
|
+
}
|
127
|
+
args[:key_path] = ::File.dirname(config[:key_filename]) if config.key?(:key_filename)
|
128
|
+
|
129
|
+
new_config = keystore_class.generate_data_key(args)
|
130
|
+
new_config[:always_add_header] = always_add_header
|
131
|
+
new_config[:encoding] = encoding
|
103
132
|
|
104
133
|
# Replace existing config entry
|
105
134
|
cfg[:ciphers].shift
|
106
|
-
cfg[:ciphers].unshift(
|
135
|
+
cfg[:ciphers].unshift(new_config)
|
107
136
|
end
|
108
137
|
full_config
|
109
138
|
end
|
@@ -112,15 +141,101 @@ module SymmetricEncryption
|
|
112
141
|
def self.dev_config
|
113
142
|
{
|
114
143
|
ciphers:
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
144
|
+
[
|
145
|
+
{
|
146
|
+
key: '1234567890ABCDEF',
|
147
|
+
iv: '1234567890ABCDEF',
|
148
|
+
cipher_name: 'aes-128-cbc',
|
149
|
+
version: 1
|
150
|
+
}
|
151
|
+
]
|
123
152
|
}
|
124
153
|
end
|
154
|
+
|
155
|
+
# Returns [Key] by recursively navigating the config tree.
|
156
|
+
#
|
157
|
+
# Supports N level deep key encrypting keys.
|
158
|
+
def self.read_key(key: nil, iv:, key_encrypting_key: nil, cipher_name: 'aes-256-cbc', keystore: nil, version: 0, **args)
|
159
|
+
if key_encrypting_key.is_a?(Hash)
|
160
|
+
# Recurse up the chain returning the parent key_encrypting_key
|
161
|
+
key_encrypting_key = read_key(cipher_name: cipher_name, **key_encrypting_key)
|
162
|
+
end
|
163
|
+
|
164
|
+
unless key
|
165
|
+
keystore_class = keystore ? constantize_symbol(keystore) : keystore_for(args)
|
166
|
+
store = keystore_class.new(key_encrypting_key: key_encrypting_key, **args)
|
167
|
+
key = store.read
|
168
|
+
end
|
169
|
+
|
170
|
+
Key.new(key: key, iv: iv, cipher_name: cipher_name)
|
171
|
+
end
|
172
|
+
|
173
|
+
#
|
174
|
+
# Internal use only methods
|
175
|
+
#
|
176
|
+
|
177
|
+
def self.keystore_for(config)
|
178
|
+
if config[:keystore]
|
179
|
+
constantize_symbol(config[:keystore])
|
180
|
+
elsif config[:encrypted_key]
|
181
|
+
Keystore::Memory
|
182
|
+
elsif config[:key_filename]
|
183
|
+
Keystore::File
|
184
|
+
elsif config[:key_env_var]
|
185
|
+
Keystore::Environment
|
186
|
+
else
|
187
|
+
raise(ArgumentError, 'Unknown keystore supplied in config')
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
def self.constantize_symbol(symbol, namespace = 'SymmetricEncryption::Keystore')
|
192
|
+
klass = "#{namespace}::#{camelize(symbol.to_s)}"
|
193
|
+
begin
|
194
|
+
Object.const_get(klass)
|
195
|
+
rescue NameError
|
196
|
+
raise(ArgumentError, "Keystore: #{symbol.inspect} not found. Looking for: #{klass}")
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
# Borrow from Rails, when not running Rails
|
201
|
+
def self.camelize(term)
|
202
|
+
string = term.to_s
|
203
|
+
string = string.sub(/^[a-z\d]*/, &:capitalize)
|
204
|
+
string.gsub!(/(?:_|(\/))([a-z\d]*)/i) { "#{Regexp.last_match(1)}#{Regexp.last_match(2).capitalize}" }
|
205
|
+
string.gsub!('/'.freeze, '::'.freeze)
|
206
|
+
string
|
207
|
+
end
|
208
|
+
|
209
|
+
# Migrate a prior config.
|
210
|
+
#
|
211
|
+
# Note:
|
212
|
+
# * The config cannot be saved back to the config file once
|
213
|
+
# migrated, without generating new Key Encrypting Keys.
|
214
|
+
# * Only run this migration in the target environment so that the
|
215
|
+
# current key encrypting files are present.
|
216
|
+
def self.migrate_config!(config)
|
217
|
+
# Backward compatibility - Deprecated
|
218
|
+
private_rsa_key = config.delete(:private_rsa_key)
|
219
|
+
|
220
|
+
# Migrate old encrypted_iv
|
221
|
+
if (encrypted_iv = config.delete(:encrypted_iv)) && private_rsa_key
|
222
|
+
encrypted_iv = RSAKey.new(private_rsa_key).decrypt(encrypted_iv)
|
223
|
+
config[:iv] = ::Base64.decode64(encrypted_iv)
|
224
|
+
end
|
225
|
+
|
226
|
+
# Migrate old iv_filename
|
227
|
+
if (file_name = config.delete(:iv_filename)) && private_rsa_key
|
228
|
+
encrypted_iv = ::File.read(file_name)
|
229
|
+
config[:iv] = RSAKey.new(private_rsa_key).decrypt(encrypted_iv)
|
230
|
+
end
|
231
|
+
|
232
|
+
# Backward compatibility - Deprecated
|
233
|
+
config[:key_encrypting_key] = RSAKey.new(private_rsa_key) if private_rsa_key
|
234
|
+
|
235
|
+
# Migrate old encrypted_key to new binary format
|
236
|
+
if (encrypted_key = config[:encrypted_key]) && private_rsa_key
|
237
|
+
config[:encrypted_key] = ::Base64.decode64(encrypted_key)
|
238
|
+
end
|
239
|
+
end
|
125
240
|
end
|
126
241
|
end
|
@@ -29,20 +29,25 @@ module SymmetricEncryption #:nodoc:
|
|
29
29
|
config.before_configuration do
|
30
30
|
# Check if already configured
|
31
31
|
unless ::SymmetricEncryption.cipher?
|
32
|
-
app_name
|
33
|
-
config_file
|
32
|
+
app_name = Rails::Application.subclasses.first.parent.to_s.underscore
|
33
|
+
config_file =
|
34
|
+
if (env_var = ENV['SYMMETRIC_ENCRYPTION_CONFIG'])
|
35
|
+
Pathname.new File.expand_path(env_var)
|
36
|
+
else
|
37
|
+
Rails.root.join('config', 'symmetric-encryption.yml')
|
38
|
+
end
|
34
39
|
if config_file.file?
|
35
40
|
begin
|
36
41
|
::SymmetricEncryption::Config.load!(file_name: config_file, env: ENV['SYMMETRIC_ENCRYPTION_ENV'] || Rails.env)
|
37
42
|
rescue ArgumentError => exc
|
38
43
|
puts "\nSymmetric Encryption not able to read keys."
|
39
44
|
puts "#{exc.class.name} #{exc.message}"
|
40
|
-
puts "To generate a new config file and key files: symmetric-encryption --generate --
|
45
|
+
puts "To generate a new config file and key files: symmetric-encryption --generate --app-name #{app_name}\n\n"
|
41
46
|
raise(exc)
|
42
47
|
end
|
43
48
|
else
|
44
49
|
puts "\nSymmetric Encryption config not found."
|
45
|
-
puts "To generate a new config file and key files: symmetric-encryption --generate --
|
50
|
+
puts "To generate a new config file and key files: symmetric-encryption --generate --app-name #{app_name}\n\n"
|
46
51
|
end
|
47
52
|
end
|
48
53
|
end
|
@@ -14,6 +14,7 @@
|
|
14
14
|
class SymmetricEncryptionValidator < ActiveModel::EachValidator
|
15
15
|
def validate_each(record, attribute, value)
|
16
16
|
return if value.blank? || SymmetricEncryption.encrypted?(value)
|
17
|
+
|
17
18
|
record.errors.add(attribute, 'must be a value encrypted using SymmetricEncryption.encrypt')
|
18
19
|
end
|
19
20
|
end
|
@@ -76,7 +76,7 @@ module SymmetricEncryption
|
|
76
76
|
# Notes:
|
77
77
|
# * Do not use this method for reading large files.
|
78
78
|
def self.read(file_name_or_stream, **args)
|
79
|
-
|
79
|
+
Reader.open(file_name_or_stream, **args, &:read)
|
80
80
|
end
|
81
81
|
|
82
82
|
# Decrypt an entire file.
|
@@ -90,22 +90,10 @@ module SymmetricEncryption
|
|
90
90
|
# target: [String|IO]
|
91
91
|
# Target file_name or IOStream
|
92
92
|
#
|
93
|
-
# block_size: [Integer]
|
94
|
-
# Number of bytes to read into memory for each read.
|
95
|
-
# For very large files using a larger block size is faster.
|
96
|
-
# Default: 65535
|
97
|
-
#
|
98
93
|
# Notes:
|
99
94
|
# * The file contents are streamed so that the entire file is _not_ loaded into memory.
|
100
|
-
def self.decrypt(source:, target:,
|
101
|
-
|
102
|
-
bytes_written = 0
|
103
|
-
self.open(source, **args) do |input_ios|
|
104
|
-
bytes_written += target_ios.write(input_ios.read(block_size)) until input_ios.eof?
|
105
|
-
end
|
106
|
-
bytes_written
|
107
|
-
ensure
|
108
|
-
target_ios.close if target_ios&.respond_to?(:closed?) && !target_ios.closed?
|
95
|
+
def self.decrypt(source:, target:, **args)
|
96
|
+
Reader.open(source, **args) { |input_file| IO.copy_stream(input_file, target) }
|
109
97
|
end
|
110
98
|
|
111
99
|
# Returns [true|false] whether the file or stream contains any data
|
@@ -132,6 +120,7 @@ module SymmetricEncryption
|
|
132
120
|
@version = version
|
133
121
|
@header_present = false
|
134
122
|
@closed = false
|
123
|
+
@read_buffer = ''.b
|
135
124
|
|
136
125
|
raise(ArgumentError, 'Buffer size cannot be smaller than 128') unless @buffer_size >= 128
|
137
126
|
|
@@ -170,6 +159,7 @@ module SymmetricEncryption
|
|
170
159
|
# ensure that the encrypted stream is closed before the stream itself is closed
|
171
160
|
def close(close_child_stream = true)
|
172
161
|
return if closed?
|
162
|
+
|
173
163
|
@ios.close if close_child_stream
|
174
164
|
@closed = true
|
175
165
|
end
|
@@ -194,35 +184,25 @@ module SymmetricEncryption
|
|
194
184
|
#
|
195
185
|
# At end of file, it returns nil if no more data is available, or the last
|
196
186
|
# remaining bytes
|
197
|
-
def read(length = nil)
|
198
|
-
data
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
if @read_buffer.
|
205
|
-
data
|
206
|
-
elsif @read_buffer.length > length
|
207
|
-
data = @read_buffer.slice!(0..length - 1)
|
187
|
+
def read(length = nil, outbuf = nil)
|
188
|
+
data = outbuf.to_s.clear
|
189
|
+
remaining_length = length
|
190
|
+
|
191
|
+
until remaining_length == 0 || eof?
|
192
|
+
read_block(remaining_length) if @read_buffer.empty?
|
193
|
+
|
194
|
+
if remaining_length && remaining_length < @read_buffer.length
|
195
|
+
data << @read_buffer.slice!(0, remaining_length)
|
208
196
|
else
|
209
|
-
data
|
210
|
-
@read_buffer
|
211
|
-
end
|
212
|
-
else
|
213
|
-
# Capture anything already in the buffer
|
214
|
-
data = @read_buffer
|
215
|
-
@read_buffer = ''
|
216
|
-
|
217
|
-
unless @ios.eof?
|
218
|
-
# Read entire file
|
219
|
-
buf = @ios.read || ''
|
220
|
-
data << @stream_cipher.update(buf) if buf && !buf.empty?
|
221
|
-
data << @stream_cipher.final
|
197
|
+
data << @read_buffer
|
198
|
+
@read_buffer.clear
|
222
199
|
end
|
200
|
+
|
201
|
+
remaining_length = length - data.length if length
|
223
202
|
end
|
203
|
+
|
224
204
|
@pos += data.length
|
225
|
-
data
|
205
|
+
data unless data.empty? && length && length.positive?
|
226
206
|
end
|
227
207
|
|
228
208
|
# Reads a single decrypted line from the file up to and including the optional sep_string.
|
@@ -242,12 +222,14 @@ module SymmetricEncryption
|
|
242
222
|
# Read more data until we get the sep_string
|
243
223
|
while (index = @read_buffer.index(sep_string)).nil? && !@ios.eof?
|
244
224
|
break if length && @read_buffer.length >= length
|
225
|
+
|
245
226
|
read_block
|
246
227
|
end
|
247
228
|
index ||= -1
|
248
|
-
data
|
249
|
-
@pos
|
229
|
+
data = @read_buffer.slice!(0..index)
|
230
|
+
@pos += data.length
|
250
231
|
return nil if data.empty? && eof?
|
232
|
+
|
251
233
|
data
|
252
234
|
end
|
253
235
|
|
@@ -272,7 +254,7 @@ module SymmetricEncryption
|
|
272
254
|
|
273
255
|
# Rewind back to the beginning of the file
|
274
256
|
def rewind
|
275
|
-
@read_buffer
|
257
|
+
@read_buffer.clear
|
276
258
|
@ios.rewind
|
277
259
|
read_header
|
278
260
|
end
|
@@ -307,10 +289,10 @@ module SymmetricEncryption
|
|
307
289
|
# Read and decrypt entire file a block at a time to get its total
|
308
290
|
# unencrypted size
|
309
291
|
size = 0
|
310
|
-
until eof
|
292
|
+
until eof?
|
311
293
|
read_block
|
312
|
-
size
|
313
|
-
@read_buffer
|
294
|
+
size += @read_buffer.size
|
295
|
+
@read_buffer.clear
|
314
296
|
end
|
315
297
|
rewind
|
316
298
|
offset = size + amount
|
@@ -328,7 +310,7 @@ module SymmetricEncryption
|
|
328
310
|
@pos = 0
|
329
311
|
|
330
312
|
# Read first block and check for the header
|
331
|
-
buf = @ios.read(@buffer_size)
|
313
|
+
buf = @ios.read(@buffer_size, @output_buffer ||= ''.b)
|
332
314
|
|
333
315
|
# Use cipher specified in header, or global cipher if it has no header
|
334
316
|
iv, key, cipher_name, cipher = nil
|
@@ -353,20 +335,30 @@ module SymmetricEncryption
|
|
353
335
|
@stream_cipher.key = key || cipher.send(:key)
|
354
336
|
@stream_cipher.iv = iv || cipher.iv
|
355
337
|
|
356
|
-
|
357
|
-
if buf && !buf.empty?
|
358
|
-
@read_buffer = @stream_cipher.update(buf)
|
359
|
-
@read_buffer << @stream_cipher.final if @ios.eof?
|
360
|
-
else
|
361
|
-
@read_buffer = ''
|
362
|
-
end
|
338
|
+
decrypt(buf)
|
363
339
|
end
|
364
340
|
|
365
341
|
# Read a block of data and append the decrypted data in the read buffer
|
366
|
-
def read_block
|
367
|
-
buf = @ios.read(@buffer_size)
|
368
|
-
|
369
|
-
|
342
|
+
def read_block(length = nil)
|
343
|
+
buf = @ios.read(length || @buffer_size, @output_buffer ||= ''.b)
|
344
|
+
decrypt(buf)
|
345
|
+
end
|
346
|
+
|
347
|
+
# Decrypts the given chunk of data and returns the result
|
348
|
+
if defined?(JRuby)
|
349
|
+
def decrypt(buf)
|
350
|
+
return if buf.nil? || buf.empty?
|
351
|
+
|
352
|
+
@read_buffer << @stream_cipher.update(buf)
|
353
|
+
@read_buffer << @stream_cipher.final if @ios.eof?
|
354
|
+
end
|
355
|
+
else
|
356
|
+
def decrypt(buf)
|
357
|
+
return if buf.nil? || buf.empty?
|
358
|
+
|
359
|
+
@read_buffer << @stream_cipher.update(buf, @cipher_buffer ||= ''.b)
|
360
|
+
@read_buffer << @stream_cipher.final if @ios.eof?
|
361
|
+
end
|
370
362
|
end
|
371
363
|
|
372
364
|
def closed?
|
@@ -55,6 +55,7 @@ module SymmetricEncryption
|
|
55
55
|
end
|
56
56
|
|
57
57
|
return @@cipher if version.nil? || (@@cipher.version == version)
|
58
|
+
|
58
59
|
secondary_ciphers.find { |c| c.version == version } || (@@cipher if version.zero?)
|
59
60
|
end
|
60
61
|
|
@@ -264,7 +265,7 @@ module SymmetricEncryption
|
|
264
265
|
# encoded_str.end_with?("\n") ? SymmetricEncryption.cipher(0) : SymmetricEncryption.cipher
|
265
266
|
# end
|
266
267
|
def self.select_cipher(&block)
|
267
|
-
@@select_cipher = block
|
268
|
+
@@select_cipher = block || nil
|
268
269
|
end
|
269
270
|
|
270
271
|
# Load the Encryption Configuration from a YAML file
|