symmetric-encryption 4.2.1 → 4.3.3

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 -5
  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.rb +13 -9
  7. data/lib/symmetric_encryption/{railties → active_record}/attr_encrypted.rb +16 -5
  8. data/lib/symmetric_encryption/active_record/encrypted_attribute.rb +37 -0
  9. data/lib/symmetric_encryption/cipher.rb +18 -14
  10. data/lib/symmetric_encryption/cli.rb +72 -54
  11. data/lib/symmetric_encryption/coerce.rb +3 -3
  12. data/lib/symmetric_encryption/config.rb +28 -27
  13. data/lib/symmetric_encryption/core.rb +25 -20
  14. data/lib/symmetric_encryption/encoder.rb +8 -8
  15. data/lib/symmetric_encryption/generator.rb +7 -3
  16. data/lib/symmetric_encryption/header.rb +24 -24
  17. data/lib/symmetric_encryption/key.rb +1 -1
  18. data/lib/symmetric_encryption/keystore.rb +23 -23
  19. data/lib/symmetric_encryption/keystore/aws.rb +10 -13
  20. data/lib/symmetric_encryption/keystore/environment.rb +5 -5
  21. data/lib/symmetric_encryption/keystore/file.rb +27 -9
  22. data/lib/symmetric_encryption/keystore/gcp.rb +21 -18
  23. data/lib/symmetric_encryption/keystore/heroku.rb +1 -1
  24. data/lib/symmetric_encryption/keystore/memory.rb +3 -3
  25. data/lib/symmetric_encryption/railtie.rb +12 -11
  26. data/lib/symmetric_encryption/railties/mongoid_encrypted.rb +5 -4
  27. data/lib/symmetric_encryption/railties/symmetric_encryption_validator.rb +1 -1
  28. data/lib/symmetric_encryption/reader.rb +13 -13
  29. data/lib/symmetric_encryption/rsa_key.rb +1 -1
  30. data/lib/symmetric_encryption/symmetric_encryption.rb +56 -36
  31. data/lib/symmetric_encryption/utils/aws.rb +8 -10
  32. data/lib/symmetric_encryption/utils/files.rb +3 -3
  33. data/lib/symmetric_encryption/utils/re_encrypt_files.rb +11 -11
  34. data/lib/symmetric_encryption/version.rb +1 -1
  35. data/lib/symmetric_encryption/writer.rb +20 -13
  36. metadata +10 -10
@@ -1,4 +1,4 @@
1
- require 'aws-sdk-kms'
1
+ require "aws-sdk-kms"
2
2
  module SymmetricEncryption
3
3
  module Keystore
4
4
  # Support AWS Key Management Service (KMS)
@@ -70,24 +70,20 @@ module SymmetricEncryption
70
70
  # ],
71
71
  # iv: 'T80pYzD0E6e/bJCdjZ6TiQ=='
72
72
  # }
73
- def self.generate_data_key(version: 0,
73
+ def self.generate_data_key(cipher_name:, app_name:, environment:, key_path:, version: 0,
74
74
  regions: Utils::Aws::AWS_US_REGIONS,
75
75
  dek: nil,
76
- cipher_name:,
77
- app_name:,
78
- environment:,
79
- key_path:,
80
- **args)
76
+ **_args)
81
77
 
82
78
  # TODO: Also support generating environment variables instead of files.
83
79
 
84
80
  version >= 255 ? (version = 1) : (version += 1)
85
- regions = Array(regions).dup
81
+ regions = Array(regions).dup
86
82
 
87
83
  master_key_alias = master_key_alias(app_name, environment)
88
84
 
89
85
  # File per region for holding the encrypted data key
90
- key_files = regions.collect do |region|
86
+ key_files = regions.collect do |region|
91
87
  file_name = "#{app_name}_#{environment}_#{region}_v#{version}.encrypted_key"
92
88
  {region: region, file_name: ::File.join(key_path, file_name)}
93
89
  end
@@ -116,12 +112,13 @@ module SymmetricEncryption
116
112
 
117
113
  # Stores the Encryption key in a file.
118
114
  # Secures the Encryption key by encrypting it with a key encryption key.
119
- def initialize(region: nil, key_files:, master_key_alias:, key_encrypting_key: nil)
115
+ def initialize(key_files:, master_key_alias:, region: nil, key_encrypting_key: nil)
120
116
  @key_files = key_files
121
117
  @master_key_alias = master_key_alias
122
- @region = region || ENV['AWS_REGION'] || ENV['AWS_DEFAULT_REGION'] || ::Aws.config[:region]
118
+ @region = region || ENV["AWS_REGION"] || ENV["AWS_DEFAULT_REGION"] || ::Aws.config[:region]
123
119
  if key_encrypting_key
124
- raise(SymmetricEncryption::ConfigError, 'AWS KMS keystore encrypts the key itself, so does not support supplying a key_encrypting_key')
120
+ raise(SymmetricEncryption::ConfigError,
121
+ "AWS KMS keystore encrypts the key itself, so does not support supplying a key_encrypting_key")
125
122
  end
126
123
  end
127
124
 
@@ -143,7 +140,7 @@ module SymmetricEncryption
143
140
  region = key_file[:region]
144
141
  file_name = key_file[:file_name]
145
142
 
146
- raise(ArgumentError, 'region and file_name are mandatory for each key_file entry') unless region && file_name
143
+ raise(ArgumentError, "region and file_name are mandatory for each key_file entry") unless region && file_name
147
144
 
148
145
  encrypted_data_key = aws(region).encrypt(data_key)
149
146
  write_encoded_to_file(file_name, encrypted_data_key)
@@ -7,13 +7,13 @@ module SymmetricEncryption
7
7
  # Returns [Hash] a new keystore configuration after generating the data key.
8
8
  #
9
9
  # Increments the supplied version number by 1.
10
- def self.generate_data_key(cipher_name:, app_name:, environment:, version: 0, dek: nil, **args)
10
+ def self.generate_data_key(cipher_name:, app_name:, environment:, version: 0, dek: nil, **_args)
11
11
  version >= 255 ? (version = 1) : (version += 1)
12
12
 
13
- kek = SymmetricEncryption::Key.new(cipher_name: cipher_name)
13
+ kek = SymmetricEncryption::Key.new(cipher_name: cipher_name)
14
14
  dek ||= SymmetricEncryption::Key.new(cipher_name: cipher_name)
15
15
 
16
- key_env_var = "#{app_name}_#{environment}_v#{version}".upcase.tr('-', '_')
16
+ key_env_var = "#{app_name}_#{environment}_v#{version}".upcase.tr("-", "_")
17
17
  new(key_env_var: key_env_var, key_encrypting_key: kek).write(dek.key)
18
18
 
19
19
  {
@@ -50,9 +50,9 @@ module SymmetricEncryption
50
50
  def write(key)
51
51
  encrypted_key = key_encrypting_key.encrypt(key)
52
52
  puts "\n\n********************************************************************************"
53
- puts 'Set the environment variable as follows:'
53
+ puts "Set the environment variable as follows:"
54
54
  puts " export #{key_env_var}=\"#{encoder.encode(encrypted_key)}\""
55
- puts '********************************************************************************'
55
+ puts "********************************************************************************"
56
56
  end
57
57
 
58
58
  private
@@ -2,17 +2,18 @@ module SymmetricEncryption
2
2
  module Keystore
3
3
  class File
4
4
  include Utils::Files
5
+ ALLOWED_PERMISSIONS = %w[100600 100400].freeze
5
6
 
6
7
  attr_accessor :file_name, :key_encrypting_key
7
8
 
8
9
  # Returns [Hash] a new keystore configuration after generating the data key.
9
10
  #
10
11
  # Increments the supplied version number by 1.
11
- def self.generate_data_key(key_path:, cipher_name:, app_name:, environment:, version: 0, dek: nil, **args)
12
+ def self.generate_data_key(key_path:, cipher_name:, app_name:, environment:, version: 0, dek: nil, **_args)
12
13
  version >= 255 ? (version = 1) : (version += 1)
13
14
 
14
15
  dek ||= SymmetricEncryption::Key.new(cipher_name: cipher_name)
15
- kek = SymmetricEncryption::Key.new(cipher_name: cipher_name)
16
+ kek = SymmetricEncryption::Key.new(cipher_name: cipher_name)
16
17
  kekek = SymmetricEncryption::Key.new(cipher_name: cipher_name)
17
18
 
18
19
  dek_file_name = ::File.join(key_path, "#{app_name}_#{environment}_v#{version}.encrypted_key")
@@ -47,11 +48,22 @@ module SymmetricEncryption
47
48
 
48
49
  # Returns the Encryption key in the clear.
49
50
  def read
50
- raise(SymmetricEncryption::ConfigError,
51
- "Symmetric Encryption key file: '#{file_name}' not found") unless ::File.exists?(file_name)
52
- raise(SymmetricEncryption::ConfigError,
53
- "Symmetric Encryption key file '#{file_name}' has the wrong "\
54
- "permissions: #{::File.stat(file_name).mode.to_s(8)}. Expected 100600 or 100400.") unless correct_permissions?
51
+ unless ::File.exist?(file_name)
52
+ raise(SymmetricEncryption::ConfigError,
53
+ "Symmetric Encryption key file: '#{file_name}' not found")
54
+ end
55
+ unless correct_permissions?
56
+ raise(SymmetricEncryption::ConfigError,
57
+ "Symmetric Encryption key file '#{file_name}' has the wrong "\
58
+ "permissions: #{::File.stat(file_name).mode.to_s(8)}. Expected 100600 or 100400.")
59
+ end
60
+ unless owned?
61
+ raise(SymmetricEncryption::ConfigError,
62
+ "Symmetric Encryption key file '#{file_name}' has the wrong "\
63
+ "owner (#{stat.uid}) or group (#{stat.gid}). "\
64
+ "Expected it to be owned by current user "\
65
+ "#{ENV['USER'] || ENV['USERNAME']}.")
66
+ end
55
67
 
56
68
  data = read_from_file(file_name)
57
69
  key_encrypting_key ? key_encrypting_key.decrypt(data) : data
@@ -69,9 +81,15 @@ module SymmetricEncryption
69
81
  # has the correct mode - readable and writable by its owner and no one
70
82
  # else, much like the keys one has in ~/.ssh
71
83
  def correct_permissions?
72
- stat = ::File.stat(file_name)
84
+ ALLOWED_PERMISSIONS.include?(stat.mode.to_s(8))
85
+ end
86
+
87
+ def owned?
88
+ stat.owned?
89
+ end
73
90
 
74
- stat.owned? && %w(100600 100400).include?(stat.mode.to_s(8))
91
+ def stat
92
+ ::File.stat(file_name)
75
93
  end
76
94
  end
77
95
  end
@@ -5,12 +5,12 @@ module SymmetricEncryption
5
5
  class Gcp
6
6
  include Utils::Files
7
7
 
8
- def self.generate_data_key(version: 0, cipher_name:, app_name:, environment:, key_path:)
8
+ def self.generate_data_key(cipher_name:, app_name:, environment:, key_path:, version: 0)
9
9
  version >= 255 ? (version = 1) : (version += 1)
10
10
 
11
- dek = SymmetricEncryption::Key.new(cipher_name: cipher_name)
11
+ dek = SymmetricEncryption::Key.new(cipher_name: cipher_name)
12
12
  file_name = "#{key_path}/#{app_name}_#{environment}_v#{version}.encrypted_key"
13
- keystore = new(
13
+ keystore = new(
14
14
  key_file: file_name,
15
15
  app_name: app_name,
16
16
  environment: environment
@@ -18,21 +18,21 @@ module SymmetricEncryption
18
18
  keystore.write(dek.key)
19
19
 
20
20
  {
21
- keystore: :gcp,
22
- cipher_name: dek.cipher_name,
23
- version: version,
24
- key_file: file_name,
25
- iv: dek.iv,
26
- crypto_key: keystore.crypto_key
21
+ keystore: :gcp,
22
+ cipher_name: dek.cipher_name,
23
+ version: version,
24
+ key_file: file_name,
25
+ iv: dek.iv,
26
+ crypto_key: keystore.crypto_key
27
27
  }
28
28
  end
29
29
 
30
30
  def initialize(key_file:, app_name: nil, environment: nil, key_encrypting_key: nil, crypto_key: nil, project_id: nil, credentials: nil, location_id: nil)
31
- @crypto_key = crypto_key
32
- @app_name = app_name
31
+ @crypto_key = crypto_key
32
+ @app_name = app_name
33
33
  @environment = environment
34
- @file_name = key_file
35
- @project_id = project_id
34
+ @file_name = key_file
35
+ @project_id = project_id
36
36
  @credentials = credentials
37
37
  @location_id = location_id
38
38
  end
@@ -46,7 +46,8 @@ module SymmetricEncryption
46
46
  end
47
47
 
48
48
  def crypto_key
49
- @crypto_key ||= self.class::KMS::KeyManagementServiceClient.crypto_key_path(project_id, location_id, app_name, environment.to_s)
49
+ @crypto_key ||= self.class::KMS::KeyManagementServiceClient.crypto_key_path(project_id, location_id, app_name,
50
+ environment.to_s)
50
51
  end
51
52
 
52
53
  private
@@ -69,18 +70,20 @@ module SymmetricEncryption
69
70
 
70
71
  def project_id
71
72
  @project_id ||= ENV["GOOGLE_CLOUD_PROJECT"]
72
- raise 'GOOGLE_CLOUD_PROJECT must be set' if @project_id.nil?
73
+ raise "GOOGLE_CLOUD_PROJECT must be set" if @project_id.nil?
74
+
73
75
  @project_id
74
76
  end
75
77
 
76
78
  def credentials
77
- @credentials ||= ENV['GOOGLE_CLOUD_KEYFILE']
78
- raise 'GOOGLE_CLOUD_KEYFILE must be set' if @credentials.nil?
79
+ @credentials ||= ENV["GOOGLE_CLOUD_KEYFILE"]
80
+ raise "GOOGLE_CLOUD_KEYFILE must be set" if @credentials.nil?
81
+
79
82
  @credentials
80
83
  end
81
84
 
82
85
  def location_id
83
- @location_id ||= ENV["GOOGLE_CLOUD_LOCATION"] || 'global'
86
+ @location_id ||= ENV["GOOGLE_CLOUD_LOCATION"] || "global"
84
87
  end
85
88
  end
86
89
  end
@@ -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
@@ -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