symmetric-encryption 4.2.1 → 4.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (36) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +5 -7
  3. data/Rakefile +9 -9
  4. data/bin/symmetric-encryption +1 -1
  5. data/lib/symmetric-encryption.rb +1 -1
  6. data/lib/symmetric_encryption/{railties → active_record}/attr_encrypted.rb +16 -5
  7. data/lib/symmetric_encryption/active_record/encrypted_attribute.rb +37 -0
  8. data/lib/symmetric_encryption/cipher.rb +20 -14
  9. data/lib/symmetric_encryption/cli.rb +72 -54
  10. data/lib/symmetric_encryption/coerce.rb +3 -3
  11. data/lib/symmetric_encryption/config.rb +28 -27
  12. data/lib/symmetric_encryption/core.rb +25 -20
  13. data/lib/symmetric_encryption/encoder.rb +26 -8
  14. data/lib/symmetric_encryption/generator.rb +7 -3
  15. data/lib/symmetric_encryption/header.rb +24 -24
  16. data/lib/symmetric_encryption/key.rb +1 -1
  17. data/lib/symmetric_encryption/keystore/aws.rb +10 -13
  18. data/lib/symmetric_encryption/keystore/environment.rb +5 -5
  19. data/lib/symmetric_encryption/keystore/file.rb +27 -9
  20. data/lib/symmetric_encryption/keystore/gcp.rb +21 -18
  21. data/lib/symmetric_encryption/keystore/heroku.rb +1 -1
  22. data/lib/symmetric_encryption/keystore/memory.rb +3 -3
  23. data/lib/symmetric_encryption/keystore.rb +23 -23
  24. data/lib/symmetric_encryption/railtie.rb +12 -11
  25. data/lib/symmetric_encryption/railties/mongoid_encrypted.rb +5 -4
  26. data/lib/symmetric_encryption/railties/symmetric_encryption_validator.rb +1 -1
  27. data/lib/symmetric_encryption/reader.rb +13 -13
  28. data/lib/symmetric_encryption/rsa_key.rb +1 -1
  29. data/lib/symmetric_encryption/symmetric_encryption.rb +56 -36
  30. data/lib/symmetric_encryption/utils/aws.rb +8 -10
  31. data/lib/symmetric_encryption/utils/files.rb +3 -3
  32. data/lib/symmetric_encryption/utils/re_encrypt_files.rb +11 -11
  33. data/lib/symmetric_encryption/version.rb +1 -1
  34. data/lib/symmetric_encryption/writer.rb +20 -13
  35. data/lib/symmetric_encryption.rb +13 -9
  36. 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, **args)
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 = SymmetricEncryption::Key.new(cipher_name: cipher_name)
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, key_encrypting_key:)
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, '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'
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: [], app_name:, rolling_deploy: false, keystore: nil)
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] || 'aes-256-cbc'
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: [], app_name:)
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 = config.delete(:version) || 1
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: '1234567890ABCDEF',
148
- iv: '1234567890ABCDEF',
149
- cipher_name: 'aes-128-cbc',
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, iv:, key_encrypting_key: nil, cipher_name: 'aes-256-cbc', keystore: nil, version: 0, **args)
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, 'Unknown keystore supplied in config')
188
+ raise(ArgumentError, "Unknown keystore supplied in config")
189
189
  end
190
190
  end
191
191
 
192
- def self.constantize_symbol(symbol, namespace = 'SymmetricEncryption::Keystore')
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!(/(?:_|(\/))([a-z\d]*)/i) { "#{Regexp.last_match(1)}#{Regexp.last_match(2).capitalize}" }
206
- string.gsub!('/'.freeze, '::'.freeze)
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 = RSAKey.new(private_rsa_key).decrypt(encrypted_iv)
224
- config[:iv] = ::Base64.decode64(encrypted_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 = config.delete(:iv_filename)) && private_rsa_key
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 = config[:encrypted_key]) && private_rsa_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
- app_name = Rails::Application.subclasses.first.parent.to_s.underscore
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 (env_var = ENV['SYMMETRIC_ENCRYPTION_CONFIG'])
35
- Pathname.new File.expand_path(env_var)
36
+ if env_var
37
+ Pathname.new(File.expand_path(env_var))
36
38
  else
37
- Rails.root.join('config', 'symmetric-encryption.yml')
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['SYMMETRIC_ENCRYPTION_ENV'] || Rails.env)
42
- rescue ArgumentError => exc
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 "#{exc.class.name} #{exc.message}"
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(exc)
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 'mongoid'
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?('encrypted_')
99
- decrypted_field_name = encrypted_field_name.to_s['encrypted_'.length..-1]
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, "SymmetricEncryption for Mongoid. Encryption enabled for field #{encrypted_field_name}. It must either start with 'encrypted_' or the option :decrypt_as must be supplied")
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, 'must be a value encrypted using SymmetricEncryption.encrypt')
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 'openssl'
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, 'rb') : 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, 'rb') { |file| new(file).header_present? }
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 = ''.b
123
+ @read_buffer = "".b
124
124
 
125
- raise(ArgumentError, 'Buffer size cannot be smaller than 128') unless @buffer_size >= 128
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.to_s.clear
188
+ data = outbuf.nil? ? "" : outbuf.clear
189
189
  remaining_length = length
190
190
 
191
- until remaining_length == 0 || eof?
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, 'End of file reached when trying to read a line')
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 = @read_buffer.slice!(0..index)
230
- @pos += data.length
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 ||= ''.b)
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 ||= ''.b)
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 ||= ''.b)
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,4 +1,4 @@
1
- require 'openssl'
1
+ require "openssl"
2
2
  module SymmetricEncryption
3
3
  # DEPRECATED - Internal use only
4
4
  class RSAKey
@@ -1,18 +1,13 @@
1
- require 'base64'
2
- require 'openssl'
3
- require 'zlib'
4
- require 'yaml'
5
- require 'erb'
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
- raise(ArgumentError, 'Cipher must respond to :encrypt and :decrypt') unless cipher.nil? || (cipher.respond_to?(:encrypt) && cipher.respond_to?(:decrypt))
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
- @@cipher = cipher
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
- 'Call SymmetricEncryption.load! or SymmetricEncryption.cipher= prior to encrypting or decrypting data'
50
+ "Call SymmetricEncryption.load! or SymmetricEncryption.cipher= prior to encrypting or decrypting data"
54
51
  )
55
52
  end
56
53
 
57
- return @@cipher if version.nil? || (@@cipher.version == version)
54
+ return @cipher if version.nil? || (@cipher.version == version)
58
55
 
59
- secondary_ciphers.find { |c| c.version == version } || (@@cipher if version.zero?)
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
- !@@cipher.nil?
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, 'secondary_ciphers must be a collection') unless secondary_ciphers.respond_to? :each
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
- raise(ArgumentError, 'secondary_ciphers can only consist of SymmetricEncryption::Ciphers') unless cipher.respond_to?(:encrypt) && cipher.respond_to?(:decrypt)
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
- @@secondary_ciphers = secondary_ciphers
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
- @@secondary_ciphers
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 @@select_cipher
146
+ elsif @select_cipher
137
147
  # Use cipher_selector if present to decide which cipher to use
138
- @@select_cipher.call(str, decoded)
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
- decrypted.force_encoding(SymmetricEncryption::BINARY_ENCODING) unless decrypted.force_encoding(SymmetricEncryption::UTF8_ENCODING).valid_encoding?
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
- # Whether the encypted value should use a random IV every time the
176
- # field is encrypted.
177
- # It is recommended to set this to true where feasible. If the encrypted
178
- # value could be used as part of a SQL where clause, or as part
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: false, compress: false, type: :string, header: cipher.always_add_header)
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
- @@select_cipher = block || nil
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 'securerandom' unless defined?(SecureRandom)
299
+ require "securerandom" unless defined?(SecureRandom)
286
300
  SecureRandom.urlsafe_base64(size)
287
301
  end
288
302
 
289
- BINARY_ENCODING = Encoding.find('binary')
290
- UTF8_ENCODING = Encoding.find('UTF-8')
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 'base64'
2
- require 'aws-sdk-kms'
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
- 'aes-256-cbc' => 'AES_256',
17
- 'aes-128-cbc' => 'AES_128'
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 = 'unknown'
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: 'Symmetric Encryption for Ruby Customer Masker Key',
112
+ description: "Symmetric Encryption for Ruby Customer Masker Key",
115
113
  tags: [
116
- {tag_key: 'CreatedAt', tag_value: Time.now.to_s},
117
- {tag_key: 'CreatedBy', tag_value: whoami}
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, 'file_name is mandatory for each key_file entry') unless file_name
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, 'wb', 0600) { |file| file.write(data) }
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, 'rb', &:read)
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