symmetric-encryption 4.3.0 → 4.4.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 +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
|