symmetric-encryption 4.3.0 → 4.4.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.
- checksums.yaml +4 -4
- data/README.md +5 -7
- data/Rakefile +9 -9
- data/bin/symmetric-encryption +1 -1
- data/lib/symmetric-encryption.rb +1 -1
- data/lib/symmetric_encryption/cipher.rb +17 -11
- data/lib/symmetric_encryption/cli.rb +72 -54
- data/lib/symmetric_encryption/coerce.rb +3 -3
- data/lib/symmetric_encryption/config.rb +28 -27
- data/lib/symmetric_encryption/core.rb +23 -22
- data/lib/symmetric_encryption/encoder.rb +26 -8
- data/lib/symmetric_encryption/generator.rb +7 -3
- data/lib/symmetric_encryption/header.rb +24 -24
- data/lib/symmetric_encryption/key.rb +1 -1
- data/lib/symmetric_encryption/keystore/aws.rb +10 -13
- data/lib/symmetric_encryption/keystore/environment.rb +5 -5
- data/lib/symmetric_encryption/keystore/file.rb +27 -9
- data/lib/symmetric_encryption/keystore/gcp.rb +21 -18
- data/lib/symmetric_encryption/keystore/heroku.rb +1 -1
- data/lib/symmetric_encryption/keystore/memory.rb +3 -3
- data/lib/symmetric_encryption/keystore.rb +23 -23
- data/lib/symmetric_encryption/railtie.rb +9 -8
- data/lib/symmetric_encryption/railties/mongoid_encrypted.rb +5 -4
- data/lib/symmetric_encryption/railties/symmetric_encryption_validator.rb +1 -1
- data/lib/symmetric_encryption/reader.rb +13 -13
- data/lib/symmetric_encryption/rsa_key.rb +1 -1
- data/lib/symmetric_encryption/symmetric_encryption.rb +24 -18
- data/lib/symmetric_encryption/utils/aws.rb +8 -10
- data/lib/symmetric_encryption/utils/files.rb +3 -3
- data/lib/symmetric_encryption/utils/re_encrypt_files.rb +11 -11
- data/lib/symmetric_encryption/version.rb +1 -1
- data/lib/symmetric_encryption/writer.rb +20 -13
- data/lib/symmetric_encryption.rb +9 -9
- metadata +8 -9
@@ -15,7 +15,7 @@ module SymmetricEncryption
|
|
15
15
|
puts "\n\n********************************************************************************"
|
16
16
|
puts "Add the environment key to Heroku:\n\n"
|
17
17
|
puts " heroku config:add #{key_env_var}=#{encoder.encode(encrypted_key)}"
|
18
|
-
puts
|
18
|
+
puts "********************************************************************************"
|
19
19
|
end
|
20
20
|
end
|
21
21
|
end
|
@@ -12,10 +12,10 @@ module SymmetricEncryption
|
|
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.generate_data_key(cipher_name:, app_name:, environment:, version: 0, dek: nil, **
|
15
|
+
def self.generate_data_key(cipher_name:, app_name:, environment:, version: 0, dek: nil, **_args)
|
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)
|
@@ -35,7 +35,7 @@ module SymmetricEncryption
|
|
35
35
|
|
36
36
|
# Stores the Encryption key in a string.
|
37
37
|
# Secures the Encryption key by encrypting it with a key encryption key.
|
38
|
-
def initialize(encrypted_key: nil
|
38
|
+
def initialize(key_encrypting_key:, encrypted_key: nil)
|
39
39
|
@encrypted_key = encrypted_key
|
40
40
|
@key_encrypting_key = key_encrypting_key
|
41
41
|
end
|
@@ -2,12 +2,12 @@ module SymmetricEncryption
|
|
2
2
|
# Encryption keys are secured in Keystores
|
3
3
|
module Keystore
|
4
4
|
# @formatter:off
|
5
|
-
autoload :Aws,
|
6
|
-
autoload :Environment,
|
7
|
-
autoload :Gcp,
|
8
|
-
autoload :File,
|
9
|
-
autoload :Heroku,
|
10
|
-
autoload :Memory,
|
5
|
+
autoload :Aws, "symmetric_encryption/keystore/aws"
|
6
|
+
autoload :Environment, "symmetric_encryption/keystore/environment"
|
7
|
+
autoload :Gcp, "symmetric_encryption/keystore/gcp"
|
8
|
+
autoload :File, "symmetric_encryption/keystore/file"
|
9
|
+
autoload :Heroku, "symmetric_encryption/keystore/heroku"
|
10
|
+
autoload :Memory, "symmetric_encryption/keystore/memory"
|
11
11
|
# @formatter:on
|
12
12
|
|
13
13
|
# Returns [Hash] a new keystore configuration after generating data keys for each environment.
|
@@ -56,7 +56,7 @@ module SymmetricEncryption
|
|
56
56
|
# Notes:
|
57
57
|
# * iv_filename is no longer supported and is removed when creating a new random cipher.
|
58
58
|
# * `iv` does not need to be encrypted and is included in the clear.
|
59
|
-
def self.rotate_keys!(full_config, environments: [],
|
59
|
+
def self.rotate_keys!(full_config, app_name:, environments: [], rolling_deploy: false, keystore: nil)
|
60
60
|
full_config.each_pair do |environment, cfg|
|
61
61
|
# Only rotate keys for specified environments. Default, all
|
62
62
|
next if !environments.empty? && !environments.include?(environment.to_sym)
|
@@ -69,7 +69,7 @@ module SymmetricEncryption
|
|
69
69
|
# Only generate new keys for keystore's that have a key encrypting key
|
70
70
|
next unless config[:key_encrypting_key] || config[:private_rsa_key]
|
71
71
|
|
72
|
-
cipher_name = config[:cipher_name] ||
|
72
|
+
cipher_name = config[:cipher_name] || "aes-256-cbc"
|
73
73
|
|
74
74
|
keystore_class = keystore ? constantize_symbol(keystore) : keystore_for(config)
|
75
75
|
|
@@ -80,7 +80,7 @@ module SymmetricEncryption
|
|
80
80
|
environment: environment
|
81
81
|
}
|
82
82
|
args[:key_path] = ::File.dirname(config[:key_filename]) if config.key?(:key_filename)
|
83
|
-
new_data_key = keystore_class.generate_data_key(args)
|
83
|
+
new_data_key = keystore_class.generate_data_key(**args)
|
84
84
|
|
85
85
|
# Add as second key so that key can be published now and only used in a later deploy.
|
86
86
|
if rolling_deploy
|
@@ -95,7 +95,7 @@ module SymmetricEncryption
|
|
95
95
|
# Rotates just the key encrypting keys for the current cipher version.
|
96
96
|
# The existing data encryption key is not changed, it is secured using the
|
97
97
|
# new key encrypting keys.
|
98
|
-
def self.rotate_key_encrypting_keys!(full_config, environments: []
|
98
|
+
def self.rotate_key_encrypting_keys!(full_config, app_name:, environments: [])
|
99
99
|
full_config.each_pair do |environment, cfg|
|
100
100
|
# Only rotate keys for specified environments. Default, all
|
101
101
|
next if !environments.empty? && !environments.include?(environment.to_sym)
|
@@ -105,7 +105,7 @@ module SymmetricEncryption
|
|
105
105
|
# Only generate new keys for keystore's that have a key encrypting key
|
106
106
|
next unless config[:key_encrypting_key]
|
107
107
|
|
108
|
-
version
|
108
|
+
version = config.delete(:version) || 1
|
109
109
|
version -= 1
|
110
110
|
|
111
111
|
always_add_header = config.delete(:always_add_header)
|
@@ -144,9 +144,9 @@ module SymmetricEncryption
|
|
144
144
|
ciphers:
|
145
145
|
[
|
146
146
|
{
|
147
|
-
key:
|
148
|
-
iv:
|
149
|
-
cipher_name:
|
147
|
+
key: "1234567890ABCDEF",
|
148
|
+
iv: "1234567890ABCDEF",
|
149
|
+
cipher_name: "aes-128-cbc",
|
150
150
|
version: 1
|
151
151
|
}
|
152
152
|
]
|
@@ -156,7 +156,7 @@ module SymmetricEncryption
|
|
156
156
|
# Returns [Key] by recursively navigating the config tree.
|
157
157
|
#
|
158
158
|
# Supports N level deep key encrypting keys.
|
159
|
-
def self.read_key(key: nil,
|
159
|
+
def self.read_key(iv:, key: nil, key_encrypting_key: nil, cipher_name: "aes-256-cbc", keystore: nil, version: 0, **args)
|
160
160
|
if key_encrypting_key.is_a?(Hash)
|
161
161
|
# Recurse up the chain returning the parent key_encrypting_key
|
162
162
|
key_encrypting_key = read_key(cipher_name: cipher_name, **key_encrypting_key)
|
@@ -185,11 +185,11 @@ module SymmetricEncryption
|
|
185
185
|
elsif config[:key_env_var]
|
186
186
|
Keystore::Environment
|
187
187
|
else
|
188
|
-
raise(ArgumentError,
|
188
|
+
raise(ArgumentError, "Unknown keystore supplied in config")
|
189
189
|
end
|
190
190
|
end
|
191
191
|
|
192
|
-
def self.constantize_symbol(symbol, namespace =
|
192
|
+
def self.constantize_symbol(symbol, namespace = "SymmetricEncryption::Keystore")
|
193
193
|
klass = "#{namespace}::#{camelize(symbol.to_s)}"
|
194
194
|
begin
|
195
195
|
Object.const_get(klass)
|
@@ -202,8 +202,8 @@ module SymmetricEncryption
|
|
202
202
|
def self.camelize(term)
|
203
203
|
string = term.to_s
|
204
204
|
string = string.sub(/^[a-z\d]*/, &:capitalize)
|
205
|
-
string.gsub!(
|
206
|
-
string.gsub!(
|
205
|
+
string.gsub!(%r{(?:_|(/))([a-z\d]*)}i) { "#{Regexp.last_match(1)}#{Regexp.last_match(2).capitalize}" }
|
206
|
+
string.gsub!("/".freeze, "::".freeze)
|
207
207
|
string
|
208
208
|
end
|
209
209
|
|
@@ -220,12 +220,12 @@ module SymmetricEncryption
|
|
220
220
|
|
221
221
|
# Migrate old encrypted_iv
|
222
222
|
if (encrypted_iv = config.delete(:encrypted_iv)) && private_rsa_key
|
223
|
-
encrypted_iv
|
224
|
-
config[:iv]
|
223
|
+
encrypted_iv = RSAKey.new(private_rsa_key).decrypt(encrypted_iv)
|
224
|
+
config[:iv] = ::Base64.decode64(encrypted_iv)
|
225
225
|
end
|
226
226
|
|
227
227
|
# Migrate old iv_filename
|
228
|
-
if (file_name
|
228
|
+
if (file_name = config.delete(:iv_filename)) && private_rsa_key
|
229
229
|
encrypted_iv = ::File.read(file_name)
|
230
230
|
config[:iv] = RSAKey.new(private_rsa_key).decrypt(encrypted_iv)
|
231
231
|
end
|
@@ -234,7 +234,7 @@ module SymmetricEncryption
|
|
234
234
|
config[:key_encrypting_key] = RSAKey.new(private_rsa_key) if private_rsa_key
|
235
235
|
|
236
236
|
# Migrate old encrypted_key to new binary format
|
237
|
-
if (encrypted_key
|
237
|
+
if (encrypted_key = config[:encrypted_key]) && private_rsa_key
|
238
238
|
config[:encrypted_key] = ::Base64.decode64(encrypted_key)
|
239
239
|
end
|
240
240
|
end
|
@@ -29,23 +29,24 @@ module SymmetricEncryption #:nodoc:
|
|
29
29
|
config.before_configuration do
|
30
30
|
# Check if already configured
|
31
31
|
unless ::SymmetricEncryption.cipher?
|
32
|
-
|
33
|
-
|
34
|
-
|
32
|
+
parent_method = Module.method_defined?(:module_parent) ? "module_parent" : "parent"
|
33
|
+
app_name = Rails::Application.subclasses.first.send(parent_method).to_s.underscore
|
34
|
+
env_var = ENV["SYMMETRIC_ENCRYPTION_CONFIG"]
|
35
|
+
config_file =
|
35
36
|
if env_var
|
36
37
|
Pathname.new(File.expand_path(env_var))
|
37
38
|
else
|
38
|
-
Rails.root.join(
|
39
|
+
Rails.root.join("config", "symmetric-encryption.yml")
|
39
40
|
end
|
40
41
|
|
41
42
|
if config_file.file?
|
42
43
|
begin
|
43
|
-
::SymmetricEncryption::Config.load!(file_name: config_file, env: ENV[
|
44
|
-
rescue ArgumentError =>
|
44
|
+
::SymmetricEncryption::Config.load!(file_name: config_file, env: ENV["SYMMETRIC_ENCRYPTION_ENV"] || Rails.env)
|
45
|
+
rescue ArgumentError => e
|
45
46
|
puts "\nSymmetric Encryption not able to read keys."
|
46
|
-
puts "#{
|
47
|
+
puts "#{e.class.name} #{e.message}"
|
47
48
|
puts "To generate a new config file and key files: symmetric-encryption --generate --app-name #{app_name}\n\n"
|
48
|
-
raise(
|
49
|
+
raise(e)
|
49
50
|
end
|
50
51
|
end
|
51
52
|
|
@@ -1,4 +1,4 @@
|
|
1
|
-
require
|
1
|
+
require "mongoid"
|
2
2
|
# Add :encrypted option for Mongoid models
|
3
3
|
#
|
4
4
|
# Example:
|
@@ -95,12 +95,13 @@ Mongoid::Fields.option :encrypted do |model, field, options|
|
|
95
95
|
|
96
96
|
# Support overriding the name of the decrypted attribute
|
97
97
|
decrypted_field_name = options.delete(:decrypt_as)
|
98
|
-
if decrypted_field_name.nil? && encrypted_field_name.to_s.start_with?(
|
99
|
-
decrypted_field_name = encrypted_field_name.to_s[
|
98
|
+
if decrypted_field_name.nil? && encrypted_field_name.to_s.start_with?("encrypted_")
|
99
|
+
decrypted_field_name = encrypted_field_name.to_s["encrypted_".length..-1]
|
100
100
|
end
|
101
101
|
|
102
102
|
if decrypted_field_name.nil?
|
103
|
-
raise(ArgumentError,
|
103
|
+
raise(ArgumentError,
|
104
|
+
"SymmetricEncryption for Mongoid. Encryption enabled for field #{encrypted_field_name}. It must either start with 'encrypted_' or the option :decrypt_as must be supplied")
|
104
105
|
end
|
105
106
|
|
106
107
|
SymmetricEncryption::Generator.generate_decrypted_accessors(model, decrypted_field_name, encrypted_field_name, options)
|
@@ -15,6 +15,6 @@ 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,
|
18
|
+
record.errors.add(attribute, "must be a value encrypted using SymmetricEncryption.encrypt")
|
19
19
|
end
|
20
20
|
end
|
@@ -1,4 +1,4 @@
|
|
1
|
-
require
|
1
|
+
require "openssl"
|
2
2
|
|
3
3
|
module SymmetricEncryption
|
4
4
|
# Read from encrypted files and other IO streams
|
@@ -60,7 +60,7 @@ module SymmetricEncryption
|
|
60
60
|
# csv.close if csv
|
61
61
|
# end
|
62
62
|
def self.open(file_name_or_stream, buffer_size: 16_384, **args, &block)
|
63
|
-
ios = file_name_or_stream.is_a?(String) ? ::File.open(file_name_or_stream,
|
63
|
+
ios = file_name_or_stream.is_a?(String) ? ::File.open(file_name_or_stream, "rb") : file_name_or_stream
|
64
64
|
|
65
65
|
begin
|
66
66
|
file = new(ios, buffer_size: buffer_size, **args)
|
@@ -104,7 +104,7 @@ module SymmetricEncryption
|
|
104
104
|
|
105
105
|
# Returns [true|false] whether the file contains the encryption header
|
106
106
|
def self.header_present?(file_name)
|
107
|
-
::File.open(file_name,
|
107
|
+
::File.open(file_name, "rb") { |file| new(file).header_present? }
|
108
108
|
end
|
109
109
|
|
110
110
|
# After opening a file Returns [true|false] whether the file being
|
@@ -120,9 +120,9 @@ module SymmetricEncryption
|
|
120
120
|
@version = version
|
121
121
|
@header_present = false
|
122
122
|
@closed = false
|
123
|
-
@read_buffer =
|
123
|
+
@read_buffer = "".b
|
124
124
|
|
125
|
-
raise(ArgumentError,
|
125
|
+
raise(ArgumentError, "Buffer size cannot be smaller than 128") unless @buffer_size >= 128
|
126
126
|
|
127
127
|
read_header
|
128
128
|
end
|
@@ -185,10 +185,10 @@ module SymmetricEncryption
|
|
185
185
|
# At end of file, it returns nil if no more data is available, or the last
|
186
186
|
# remaining bytes
|
187
187
|
def read(length = nil, outbuf = nil)
|
188
|
-
data = outbuf.
|
188
|
+
data = outbuf.nil? ? "" : outbuf.clear
|
189
189
|
remaining_length = length
|
190
190
|
|
191
|
-
until remaining_length
|
191
|
+
until remaining_length&.zero? || eof?
|
192
192
|
read_block(remaining_length) if @read_buffer.empty?
|
193
193
|
|
194
194
|
if remaining_length && remaining_length < @read_buffer.length
|
@@ -209,7 +209,7 @@ module SymmetricEncryption
|
|
209
209
|
# Raises EOFError on eof
|
210
210
|
# The stream must be opened for reading or an IOError will be raised.
|
211
211
|
def readline(sep_string = "\n")
|
212
|
-
gets(sep_string) || raise(EOFError,
|
212
|
+
gets(sep_string) || raise(EOFError, "End of file reached when trying to read a line")
|
213
213
|
end
|
214
214
|
|
215
215
|
# Reads a single decrypted line from the file up to and including the optional sep_string.
|
@@ -226,8 +226,8 @@ module SymmetricEncryption
|
|
226
226
|
read_block
|
227
227
|
end
|
228
228
|
index ||= -1
|
229
|
-
data
|
230
|
-
@pos
|
229
|
+
data = @read_buffer.slice!(0..index)
|
230
|
+
@pos += data.length
|
231
231
|
return nil if data.empty? && eof?
|
232
232
|
|
233
233
|
data
|
@@ -310,7 +310,7 @@ module SymmetricEncryption
|
|
310
310
|
@pos = 0
|
311
311
|
|
312
312
|
# Read first block and check for the header
|
313
|
-
buf = @ios.read(@buffer_size, @output_buffer ||=
|
313
|
+
buf = @ios.read(@buffer_size, @output_buffer ||= "".b)
|
314
314
|
|
315
315
|
# Use cipher specified in header, or global cipher if it has no header
|
316
316
|
iv, key, cipher_name, cipher = nil
|
@@ -340,7 +340,7 @@ module SymmetricEncryption
|
|
340
340
|
|
341
341
|
# Read a block of data and append the decrypted data in the read buffer
|
342
342
|
def read_block(length = nil)
|
343
|
-
buf = @ios.read(length || @buffer_size, @output_buffer ||=
|
343
|
+
buf = @ios.read(length || @buffer_size, @output_buffer ||= "".b)
|
344
344
|
decrypt(buf)
|
345
345
|
end
|
346
346
|
|
@@ -356,7 +356,7 @@ module SymmetricEncryption
|
|
356
356
|
def decrypt(buf)
|
357
357
|
return if buf.nil? || buf.empty?
|
358
358
|
|
359
|
-
@read_buffer << @stream_cipher.update(buf, @cipher_buffer ||=
|
359
|
+
@read_buffer << @stream_cipher.update(buf, @cipher_buffer ||= "".b)
|
360
360
|
@read_buffer << @stream_cipher.final if @ios.eof?
|
361
361
|
end
|
362
362
|
end
|
@@ -1,8 +1,8 @@
|
|
1
|
-
require
|
2
|
-
require
|
3
|
-
require
|
4
|
-
require
|
5
|
-
require
|
1
|
+
require "base64"
|
2
|
+
require "openssl"
|
3
|
+
require "zlib"
|
4
|
+
require "yaml"
|
5
|
+
require "erb"
|
6
6
|
|
7
7
|
# Encrypt using 256 Bit AES CBC symmetric key and initialization vector
|
8
8
|
# The symmetric key is protected using the private key below and must
|
@@ -32,7 +32,9 @@ module SymmetricEncryption
|
|
32
32
|
# cipher: 'aes-128-cbc'
|
33
33
|
# )
|
34
34
|
def self.cipher=(cipher)
|
35
|
-
|
35
|
+
unless cipher.nil? || (cipher.respond_to?(:encrypt) && cipher.respond_to?(:decrypt))
|
36
|
+
raise(ArgumentError, "Cipher must respond to :encrypt and :decrypt")
|
37
|
+
end
|
36
38
|
|
37
39
|
@cipher = cipher
|
38
40
|
end
|
@@ -45,7 +47,7 @@ module SymmetricEncryption
|
|
45
47
|
unless cipher?
|
46
48
|
raise(
|
47
49
|
SymmetricEncryption::ConfigError,
|
48
|
-
|
50
|
+
"Call SymmetricEncryption.load! or SymmetricEncryption.cipher= prior to encrypting or decrypting data"
|
49
51
|
)
|
50
52
|
end
|
51
53
|
|
@@ -61,10 +63,12 @@ module SymmetricEncryption
|
|
61
63
|
|
62
64
|
# Set the Secondary Symmetric Ciphers Array to be used
|
63
65
|
def self.secondary_ciphers=(secondary_ciphers)
|
64
|
-
raise(ArgumentError,
|
66
|
+
raise(ArgumentError, "secondary_ciphers must be a collection") unless secondary_ciphers.respond_to? :each
|
65
67
|
|
66
68
|
secondary_ciphers.each do |cipher|
|
67
|
-
|
69
|
+
unless cipher.respond_to?(:encrypt) && cipher.respond_to?(:decrypt)
|
70
|
+
raise(ArgumentError, "secondary_ciphers can only consist of SymmetricEncryption::Ciphers")
|
71
|
+
end
|
68
72
|
end
|
69
73
|
@secondary_ciphers = secondary_ciphers
|
70
74
|
end
|
@@ -121,7 +125,7 @@ module SymmetricEncryption
|
|
121
125
|
# the incorrect key. Clearly the data returned is garbage, but it still
|
122
126
|
# successfully returns a string of data
|
123
127
|
def self.decrypt(encrypted_and_encoded_string, version: nil, type: :string)
|
124
|
-
return encrypted_and_encoded_string if encrypted_and_encoded_string.nil? || (encrypted_and_encoded_string ==
|
128
|
+
return encrypted_and_encoded_string if encrypted_and_encoded_string.nil? || (encrypted_and_encoded_string == "")
|
125
129
|
|
126
130
|
str = encrypted_and_encoded_string.to_s
|
127
131
|
|
@@ -150,14 +154,16 @@ module SymmetricEncryption
|
|
150
154
|
end
|
151
155
|
|
152
156
|
# Try to force result to UTF-8 encoding, but if it is not valid, force it back to Binary
|
153
|
-
|
157
|
+
unless decrypted.force_encoding(SymmetricEncryption::UTF8_ENCODING).valid_encoding?
|
158
|
+
decrypted.force_encoding(SymmetricEncryption::BINARY_ENCODING)
|
159
|
+
end
|
154
160
|
Coerce.coerce_from_string(decrypted, type)
|
155
161
|
end
|
156
162
|
|
157
163
|
# Returns the header for the encrypted string
|
158
164
|
# Returns [nil] if no header is present
|
159
165
|
def self.header(encrypted_and_encoded_string)
|
160
|
-
return if encrypted_and_encoded_string.nil? || (encrypted_and_encoded_string ==
|
166
|
+
return if encrypted_and_encoded_string.nil? || (encrypted_and_encoded_string == "")
|
161
167
|
|
162
168
|
# Decode before decrypting supplied string
|
163
169
|
decoded = cipher.encoder.decode(encrypted_and_encoded_string.to_s)
|
@@ -212,7 +218,7 @@ module SymmetricEncryption
|
|
212
218
|
# the coercible gem is available in the path.
|
213
219
|
# Default: :string
|
214
220
|
def self.encrypt(str, random_iv: SymmetricEncryption.randomize_iv?, compress: false, type: :string, header: cipher.always_add_header)
|
215
|
-
return str if str.nil? || (str ==
|
221
|
+
return str if str.nil? || (str == "")
|
216
222
|
|
217
223
|
# Encrypt and then encode the supplied string
|
218
224
|
cipher.encrypt(Coerce.coerce_to_string(str, type), random_iv: random_iv, compress: compress, header: header)
|
@@ -241,7 +247,7 @@ module SymmetricEncryption
|
|
241
247
|
# * This method only works reliably when the encrypted data includes the symmetric encryption header.
|
242
248
|
# * nil and '' are considered "encrypted" so that validations do not blow up on empty values.
|
243
249
|
def self.encrypted?(encrypted_data)
|
244
|
-
return false if encrypted_data.nil? || (encrypted_data ==
|
250
|
+
return false if encrypted_data.nil? || (encrypted_data == "")
|
245
251
|
|
246
252
|
@header ||= SymmetricEncryption.cipher.encoded_magic_header
|
247
253
|
encrypted_data.to_s.start_with?(@header)
|
@@ -290,16 +296,16 @@ module SymmetricEncryption
|
|
290
296
|
|
291
297
|
# Generate a Random password
|
292
298
|
def self.random_password(size = 22)
|
293
|
-
require
|
299
|
+
require "securerandom" unless defined?(SecureRandom)
|
294
300
|
SecureRandom.urlsafe_base64(size)
|
295
301
|
end
|
296
302
|
|
297
|
-
BINARY_ENCODING = Encoding.find(
|
298
|
-
UTF8_ENCODING = Encoding.find(
|
303
|
+
BINARY_ENCODING = Encoding.find("binary")
|
304
|
+
UTF8_ENCODING = Encoding.find("UTF-8")
|
299
305
|
|
300
306
|
# Defaults
|
301
307
|
@cipher = nil
|
302
308
|
@secondary_ciphers = []
|
303
309
|
@select_cipher = nil
|
304
|
-
@
|
310
|
+
@randomize_iv = false
|
305
311
|
end
|
@@ -1,5 +1,5 @@
|
|
1
|
-
require
|
2
|
-
require
|
1
|
+
require "base64"
|
2
|
+
require "aws-sdk-kms"
|
3
3
|
module SymmetricEncryption
|
4
4
|
module Utils
|
5
5
|
# Wrap the AWS KMS client so that it automatically creates the Customer Master Key,
|
@@ -13,8 +13,8 @@ module SymmetricEncryption
|
|
13
13
|
|
14
14
|
# TODO: Map to OpenSSL ciphers
|
15
15
|
AWS_KEY_SPEC_MAP = {
|
16
|
-
|
17
|
-
|
16
|
+
"aes-256-cbc" => "AES_256",
|
17
|
+
"aes-128-cbc" => "AES_128"
|
18
18
|
}.freeze
|
19
19
|
|
20
20
|
# TODO: Move to Keystore::Aws
|
@@ -98,12 +98,10 @@ module SymmetricEncryption
|
|
98
98
|
|
99
99
|
private
|
100
100
|
|
101
|
-
attr_reader :client
|
102
|
-
|
103
101
|
def whoami
|
104
102
|
@whoami ||= `whoami`.strip
|
105
103
|
rescue StandardError
|
106
|
-
@whoami =
|
104
|
+
@whoami = "unknown"
|
107
105
|
end
|
108
106
|
|
109
107
|
# Creates a new Customer Master Key for Symmetric Encryption use.
|
@@ -111,10 +109,10 @@ module SymmetricEncryption
|
|
111
109
|
# TODO: Add error handling and retry
|
112
110
|
|
113
111
|
resp = client.create_key(
|
114
|
-
description:
|
112
|
+
description: "Symmetric Encryption for Ruby Customer Masker Key",
|
115
113
|
tags: [
|
116
|
-
{tag_key:
|
117
|
-
{tag_key:
|
114
|
+
{tag_key: "CreatedAt", tag_value: Time.now.to_s},
|
115
|
+
{tag_key: "CreatedBy", tag_value: whoami}
|
118
116
|
]
|
119
117
|
)
|
120
118
|
resp.key_metadata.key_id
|
@@ -6,7 +6,7 @@ module SymmetricEncryption
|
|
6
6
|
attr_reader :file_name
|
7
7
|
|
8
8
|
def read_file_and_decode(file_name)
|
9
|
-
raise(SymmetricEncryption::ConfigError,
|
9
|
+
raise(SymmetricEncryption::ConfigError, "file_name is mandatory for each key_file entry") unless file_name
|
10
10
|
|
11
11
|
raise(SymmetricEncryption::ConfigError, "File #{file_name} could not be found") unless ::File.exist?(file_name)
|
12
12
|
|
@@ -31,12 +31,12 @@ module SymmetricEncryption
|
|
31
31
|
key_path = ::File.dirname(file_name)
|
32
32
|
::FileUtils.mkdir_p(key_path) unless ::File.directory?(key_path)
|
33
33
|
::File.rename(file_name, "#{file_name}.#{Time.now.to_i}") if ::File.exist?(file_name)
|
34
|
-
::File.open(file_name,
|
34
|
+
::File.open(file_name, "wb", 0o600) { |file| file.write(data) }
|
35
35
|
end
|
36
36
|
|
37
37
|
# Read from the file, raising an exception if it is not found
|
38
38
|
def read_from_file(file_name)
|
39
|
-
::File.open(file_name,
|
39
|
+
::File.open(file_name, "rb", &:read)
|
40
40
|
rescue Errno::ENOENT
|
41
41
|
raise(SymmetricEncryption::ConfigError, "Symmetric Encryption key file: '#{file_name}' not found or readable")
|
42
42
|
end
|
@@ -55,26 +55,26 @@ module SymmetricEncryption
|
|
55
55
|
lines = File.read(file_name)
|
56
56
|
hits, output_lines = re_encrypt_lines(lines)
|
57
57
|
|
58
|
-
File.open(file_name,
|
58
|
+
File.open(file_name, "wb") { |file| file.write(output_lines) } if hits.positive?
|
59
59
|
hits
|
60
60
|
end
|
61
61
|
|
62
62
|
# Replaces instances of encrypted data within lines of text with re-encrypted values
|
63
63
|
def re_encrypt_lines(lines)
|
64
64
|
hits = 0
|
65
|
-
output_lines =
|
65
|
+
output_lines = ""
|
66
66
|
r = regexp
|
67
67
|
lines.each_line do |line|
|
68
68
|
line.force_encoding(SymmetricEncryption::UTF8_ENCODING)
|
69
69
|
output_lines <<
|
70
70
|
if line.valid_encoding? && (result = line.match(r))
|
71
|
-
encrypted
|
72
|
-
new_value
|
73
|
-
if new_value
|
71
|
+
encrypted = result[0]
|
72
|
+
new_value = re_encrypt(encrypted)
|
73
|
+
if new_value == encrypted
|
74
|
+
line
|
75
|
+
else
|
74
76
|
hits += 1
|
75
77
|
line.gsub(encrypted, new_value)
|
76
|
-
else
|
77
|
-
line
|
78
78
|
end
|
79
79
|
else
|
80
80
|
line
|
@@ -117,8 +117,8 @@ module SymmetricEncryption
|
|
117
117
|
begin
|
118
118
|
count = re_encrypt_contents(file_name)
|
119
119
|
puts "Re-encrypted #{count} encrypted value(s) in: #{file_name}" if count.positive?
|
120
|
-
rescue StandardError =>
|
121
|
-
puts "Failed re-encrypting the file contents of: #{file_name}. #{
|
120
|
+
rescue StandardError => e
|
121
|
+
puts "Failed re-encrypting the file contents of: #{file_name}. #{e.class.name}: #{e.message}"
|
122
122
|
end
|
123
123
|
end
|
124
124
|
end
|
@@ -127,13 +127,13 @@ module SymmetricEncryption
|
|
127
127
|
private
|
128
128
|
|
129
129
|
def regexp
|
130
|
-
@regexp ||=
|
130
|
+
@regexp ||= %r{#{SymmetricEncryption.cipher.encoded_magic_header}([A-Za-z0-9+/]+[=\\n]*)}
|
131
131
|
end
|
132
132
|
|
133
133
|
# Returns [Integer] encrypted file key version.
|
134
134
|
# Returns [nil] if the file is not encrypted or does not have a header.
|
135
135
|
def encrypted_file_version(file_name)
|
136
|
-
::File.open(file_name,
|
136
|
+
::File.open(file_name, "rb") do |file|
|
137
137
|
reader = SymmetricEncryption::Reader.new(file)
|
138
138
|
reader.version if reader.header_present?
|
139
139
|
end
|
@@ -1,4 +1,4 @@
|
|
1
|
-
require
|
1
|
+
require "openssl"
|
2
2
|
|
3
3
|
module SymmetricEncryption
|
4
4
|
# Write to encrypted files and other IO streams.
|
@@ -49,10 +49,10 @@ module SymmetricEncryption
|
|
49
49
|
# end
|
50
50
|
def self.open(file_name_or_stream, compress: nil, **args)
|
51
51
|
if file_name_or_stream.is_a?(String)
|
52
|
-
file_name_or_stream = ::File.open(file_name_or_stream,
|
52
|
+
file_name_or_stream = ::File.open(file_name_or_stream, "wb")
|
53
53
|
compress = !(/\.(zip|gz|gzip|xls.|)\z/i === file_name_or_stream) if compress.nil?
|
54
|
-
|
55
|
-
compress = true
|
54
|
+
elsif compress.nil?
|
55
|
+
compress = true
|
56
56
|
end
|
57
57
|
|
58
58
|
begin
|
@@ -97,15 +97,22 @@ module SymmetricEncryption
|
|
97
97
|
def initialize(ios, version: nil, cipher_name: nil, header: true, random_key: true, random_iv: true, compress: false)
|
98
98
|
# Compress is only used at this point for setting the flag in the header
|
99
99
|
@ios = ios
|
100
|
-
raise(ArgumentError,
|
101
|
-
|
100
|
+
raise(ArgumentError, "When :random_key is true, :random_iv must also be true") if random_key && !random_iv
|
101
|
+
if cipher_name && !random_key && !random_iv
|
102
|
+
raise(ArgumentError, "Cannot supply a :cipher_name unless both :random_key and :random_iv are true")
|
103
|
+
end
|
102
104
|
|
103
105
|
# Cipher to encrypt the random_key, or the entire file
|
104
106
|
cipher = SymmetricEncryption.cipher(version)
|
105
|
-
|
107
|
+
unless cipher
|
108
|
+
raise(SymmetricEncryption::CipherError,
|
109
|
+
"Cipher with version:#{version} not found in any of the configured SymmetricEncryption ciphers")
|
110
|
+
end
|
106
111
|
|
107
112
|
# Force header if compressed or using random iv, key
|
108
|
-
|
113
|
+
if (header == true) || compress || random_key || random_iv
|
114
|
+
header = Header.new(version: cipher.version, compress: compress, cipher_name: cipher_name)
|
115
|
+
end
|
109
116
|
|
110
117
|
@stream_cipher = ::OpenSSL::Cipher.new(cipher_name || cipher.cipher_name)
|
111
118
|
@stream_cipher.encrypt
|
@@ -158,8 +165,8 @@ module SymmetricEncryption
|
|
158
165
|
def write(data)
|
159
166
|
return unless data
|
160
167
|
|
161
|
-
bytes
|
162
|
-
@size
|
168
|
+
bytes = data.to_s
|
169
|
+
@size += bytes.size
|
163
170
|
partial = @stream_cipher.update(bytes)
|
164
171
|
@ios.write(partial) unless partial.empty?
|
165
172
|
data.length
|
@@ -168,9 +175,9 @@ module SymmetricEncryption
|
|
168
175
|
def write(data)
|
169
176
|
return unless data
|
170
177
|
|
171
|
-
bytes
|
172
|
-
@size
|
173
|
-
partial = @stream_cipher.update(bytes, @cipher_buffer ||=
|
178
|
+
bytes = data.to_s
|
179
|
+
@size += bytes.size
|
180
|
+
partial = @stream_cipher.update(bytes, @cipher_buffer ||= "".b)
|
174
181
|
@ios.write(partial) unless partial.empty?
|
175
182
|
data.length
|
176
183
|
end
|