symmetric-encryption 4.2.1 → 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/{railties → active_record}/attr_encrypted.rb +16 -5
- data/lib/symmetric_encryption/active_record/encrypted_attribute.rb +37 -0
- data/lib/symmetric_encryption/cipher.rb +20 -14
- 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 +25 -20
- 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 +12 -11
- 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 +56 -36
- 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 +13 -9
- metadata +10 -10
@@ -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,26 +29,27 @@ module SymmetricEncryption #:nodoc:
|
|
29
29
|
config.before_configuration do
|
30
30
|
# Check if already configured
|
31
31
|
unless ::SymmetricEncryption.cipher?
|
32
|
-
|
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"]
|
33
35
|
config_file =
|
34
|
-
if
|
35
|
-
Pathname.new
|
36
|
+
if env_var
|
37
|
+
Pathname.new(File.expand_path(env_var))
|
36
38
|
else
|
37
|
-
Rails.root.join(
|
39
|
+
Rails.root.join("config", "symmetric-encryption.yml")
|
38
40
|
end
|
41
|
+
|
39
42
|
if config_file.file?
|
40
43
|
begin
|
41
|
-
::SymmetricEncryption::Config.load!(file_name: config_file, env: ENV[
|
42
|
-
rescue ArgumentError =>
|
44
|
+
::SymmetricEncryption::Config.load!(file_name: config_file, env: ENV["SYMMETRIC_ENCRYPTION_ENV"] || Rails.env)
|
45
|
+
rescue ArgumentError => e
|
43
46
|
puts "\nSymmetric Encryption not able to read keys."
|
44
|
-
puts "#{
|
47
|
+
puts "#{e.class.name} #{e.message}"
|
45
48
|
puts "To generate a new config file and key files: symmetric-encryption --generate --app-name #{app_name}\n\n"
|
46
|
-
raise(
|
49
|
+
raise(e)
|
47
50
|
end
|
48
|
-
else
|
49
|
-
puts "\nSymmetric Encryption config not found."
|
50
|
-
puts "To generate a new config file and key files: symmetric-encryption --generate --app-name #{app_name}\n\n"
|
51
51
|
end
|
52
|
+
|
52
53
|
end
|
53
54
|
end
|
54
55
|
end
|
@@ -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,18 +1,13 @@
|
|
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
|
9
9
|
# be distributed separately from the application
|
10
10
|
module SymmetricEncryption
|
11
|
-
# Defaults
|
12
|
-
@@cipher = nil
|
13
|
-
@@secondary_ciphers = []
|
14
|
-
@@select_cipher = nil
|
15
|
-
|
16
11
|
# List of types supported when encrypting or decrypting data
|
17
12
|
#
|
18
13
|
# Each type maps to the built-in Ruby types as follows:
|
@@ -37,9 +32,11 @@ module SymmetricEncryption
|
|
37
32
|
# cipher: 'aes-128-cbc'
|
38
33
|
# )
|
39
34
|
def self.cipher=(cipher)
|
40
|
-
|
35
|
+
unless cipher.nil? || (cipher.respond_to?(:encrypt) && cipher.respond_to?(:decrypt))
|
36
|
+
raise(ArgumentError, "Cipher must respond to :encrypt and :decrypt")
|
37
|
+
end
|
41
38
|
|
42
|
-
|
39
|
+
@cipher = cipher
|
43
40
|
end
|
44
41
|
|
45
42
|
# Returns the Primary Symmetric Cipher being used
|
@@ -50,33 +47,46 @@ module SymmetricEncryption
|
|
50
47
|
unless cipher?
|
51
48
|
raise(
|
52
49
|
SymmetricEncryption::ConfigError,
|
53
|
-
|
50
|
+
"Call SymmetricEncryption.load! or SymmetricEncryption.cipher= prior to encrypting or decrypting data"
|
54
51
|
)
|
55
52
|
end
|
56
53
|
|
57
|
-
return
|
54
|
+
return @cipher if version.nil? || (@cipher.version == version)
|
58
55
|
|
59
|
-
secondary_ciphers.find { |c| c.version == version } || (
|
56
|
+
secondary_ciphers.find { |c| c.version == version } || (@cipher if version.zero?)
|
60
57
|
end
|
61
58
|
|
62
59
|
# Returns whether a primary cipher has been set
|
63
60
|
def self.cipher?
|
64
|
-
|
61
|
+
!@cipher.nil?
|
65
62
|
end
|
66
63
|
|
67
64
|
# Set the Secondary Symmetric Ciphers Array to be used
|
68
65
|
def self.secondary_ciphers=(secondary_ciphers)
|
69
|
-
raise(ArgumentError,
|
66
|
+
raise(ArgumentError, "secondary_ciphers must be a collection") unless secondary_ciphers.respond_to? :each
|
70
67
|
|
71
68
|
secondary_ciphers.each do |cipher|
|
72
|
-
|
69
|
+
unless cipher.respond_to?(:encrypt) && cipher.respond_to?(:decrypt)
|
70
|
+
raise(ArgumentError, "secondary_ciphers can only consist of SymmetricEncryption::Ciphers")
|
71
|
+
end
|
73
72
|
end
|
74
|
-
|
73
|
+
@secondary_ciphers = secondary_ciphers
|
75
74
|
end
|
76
75
|
|
77
76
|
# Returns the Primary Symmetric Cipher being used
|
78
77
|
def self.secondary_ciphers
|
79
|
-
|
78
|
+
@secondary_ciphers
|
79
|
+
end
|
80
|
+
|
81
|
+
# Whether to randomize the iv by default.
|
82
|
+
# true: Generate a new random IV by default. [HIGHLY RECOMMENDED]
|
83
|
+
# false: Do not generate a new random IV by default.
|
84
|
+
def self.randomize_iv?
|
85
|
+
@randomize_iv
|
86
|
+
end
|
87
|
+
|
88
|
+
def self.randomize_iv=(randomize_iv)
|
89
|
+
@randomize_iv = randomize_iv
|
80
90
|
end
|
81
91
|
|
82
92
|
# Decrypt supplied string.
|
@@ -115,7 +125,7 @@ module SymmetricEncryption
|
|
115
125
|
# the incorrect key. Clearly the data returned is garbage, but it still
|
116
126
|
# successfully returns a string of data
|
117
127
|
def self.decrypt(encrypted_and_encoded_string, version: nil, type: :string)
|
118
|
-
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 == "")
|
119
129
|
|
120
130
|
str = encrypted_and_encoded_string.to_s
|
121
131
|
|
@@ -133,9 +143,9 @@ module SymmetricEncryption
|
|
133
143
|
if version
|
134
144
|
# Supplied version takes preference
|
135
145
|
cipher(version)
|
136
|
-
elsif
|
146
|
+
elsif @select_cipher
|
137
147
|
# Use cipher_selector if present to decide which cipher to use
|
138
|
-
|
148
|
+
@select_cipher.call(str, decoded)
|
139
149
|
else
|
140
150
|
# Global cipher
|
141
151
|
cipher
|
@@ -144,14 +154,16 @@ module SymmetricEncryption
|
|
144
154
|
end
|
145
155
|
|
146
156
|
# Try to force result to UTF-8 encoding, but if it is not valid, force it back to Binary
|
147
|
-
|
157
|
+
unless decrypted.force_encoding(SymmetricEncryption::UTF8_ENCODING).valid_encoding?
|
158
|
+
decrypted.force_encoding(SymmetricEncryption::BINARY_ENCODING)
|
159
|
+
end
|
148
160
|
Coerce.coerce_from_string(decrypted, type)
|
149
161
|
end
|
150
162
|
|
151
163
|
# Returns the header for the encrypted string
|
152
164
|
# Returns [nil] if no header is present
|
153
165
|
def self.header(encrypted_and_encoded_string)
|
154
|
-
return if encrypted_and_encoded_string.nil? || (encrypted_and_encoded_string ==
|
166
|
+
return if encrypted_and_encoded_string.nil? || (encrypted_and_encoded_string == "")
|
155
167
|
|
156
168
|
# Decode before decrypting supplied string
|
157
169
|
decoded = cipher.encoder.decode(encrypted_and_encoded_string.to_s)
|
@@ -172,10 +184,12 @@ module SymmetricEncryption
|
|
172
184
|
# to convert it to a string
|
173
185
|
#
|
174
186
|
# random_iv [true|false]
|
175
|
-
#
|
176
|
-
#
|
177
|
-
#
|
178
|
-
#
|
187
|
+
# Mandatory unless `SymmetricEncryption.randomize_iv = true` has been called.
|
188
|
+
#
|
189
|
+
# Whether the encrypted value should use a random IV every time the field is encrypted.
|
190
|
+
# It is recommended to set this to true where possible.
|
191
|
+
#
|
192
|
+
# If the encrypted value could be used as part of a SQL where clause, or as part
|
179
193
|
# of any lookup, then it must be false.
|
180
194
|
# Setting random_iv to true will result in a different encrypted output for
|
181
195
|
# the same input string.
|
@@ -203,8 +217,8 @@ module SymmetricEncryption
|
|
203
217
|
# Note: If type is set to something other than :string, it's expected that
|
204
218
|
# the coercible gem is available in the path.
|
205
219
|
# Default: :string
|
206
|
-
def self.encrypt(str, random_iv:
|
207
|
-
return str if str.nil? || (str ==
|
220
|
+
def self.encrypt(str, random_iv: SymmetricEncryption.randomize_iv?, compress: false, type: :string, header: cipher.always_add_header)
|
221
|
+
return str if str.nil? || (str == "")
|
208
222
|
|
209
223
|
# Encrypt and then encode the supplied string
|
210
224
|
cipher.encrypt(Coerce.coerce_to_string(str, type), random_iv: random_iv, compress: compress, header: header)
|
@@ -233,7 +247,7 @@ module SymmetricEncryption
|
|
233
247
|
# * This method only works reliably when the encrypted data includes the symmetric encryption header.
|
234
248
|
# * nil and '' are considered "encrypted" so that validations do not blow up on empty values.
|
235
249
|
def self.encrypted?(encrypted_data)
|
236
|
-
return false if encrypted_data.nil? || (encrypted_data ==
|
250
|
+
return false if encrypted_data.nil? || (encrypted_data == "")
|
237
251
|
|
238
252
|
@header ||= SymmetricEncryption.cipher.encoded_magic_header
|
239
253
|
encrypted_data.to_s.start_with?(@header)
|
@@ -265,7 +279,7 @@ module SymmetricEncryption
|
|
265
279
|
# encoded_str.end_with?("\n") ? SymmetricEncryption.cipher(0) : SymmetricEncryption.cipher
|
266
280
|
# end
|
267
281
|
def self.select_cipher(&block)
|
268
|
-
|
282
|
+
@select_cipher = block || nil
|
269
283
|
end
|
270
284
|
|
271
285
|
# Load the Encryption Configuration from a YAML file
|
@@ -282,10 +296,16 @@ module SymmetricEncryption
|
|
282
296
|
|
283
297
|
# Generate a Random password
|
284
298
|
def self.random_password(size = 22)
|
285
|
-
require
|
299
|
+
require "securerandom" unless defined?(SecureRandom)
|
286
300
|
SecureRandom.urlsafe_base64(size)
|
287
301
|
end
|
288
302
|
|
289
|
-
BINARY_ENCODING = Encoding.find(
|
290
|
-
UTF8_ENCODING = Encoding.find(
|
303
|
+
BINARY_ENCODING = Encoding.find("binary")
|
304
|
+
UTF8_ENCODING = Encoding.find("UTF-8")
|
305
|
+
|
306
|
+
# Defaults
|
307
|
+
@cipher = nil
|
308
|
+
@secondary_ciphers = []
|
309
|
+
@select_cipher = nil
|
310
|
+
@randomize_iv = false
|
291
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
|