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.
- checksums.yaml +4 -4
- data/README.md +5 -5
- data/Rakefile +9 -9
- data/bin/symmetric-encryption +1 -1
- data/lib/symmetric-encryption.rb +1 -1
- data/lib/symmetric_encryption.rb +13 -9
- 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 +18 -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 +8 -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.rb +23 -23
- 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/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
- metadata +10 -10
@@ -1,4 +1,4 @@
|
|
1
|
-
require
|
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
|
-
|
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
|
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
|
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,
|
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[
|
118
|
+
@region = region || ENV["AWS_REGION"] || ENV["AWS_DEFAULT_REGION"] || ::Aws.config[:region]
|
123
119
|
if key_encrypting_key
|
124
|
-
raise(SymmetricEncryption::ConfigError,
|
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,
|
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, **
|
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
|
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
|
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, **
|
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
|
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
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
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
|
84
|
+
ALLOWED_PERMISSIONS.include?(stat.mode.to_s(8))
|
85
|
+
end
|
86
|
+
|
87
|
+
def owned?
|
88
|
+
stat.owned?
|
89
|
+
end
|
73
90
|
|
74
|
-
|
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(
|
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
|
11
|
+
dek = SymmetricEncryption::Key.new(cipher_name: cipher_name)
|
12
12
|
file_name = "#{key_path}/#{app_name}_#{environment}_v#{version}.encrypted_key"
|
13
|
-
keystore
|
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:
|
22
|
-
cipher_name:
|
23
|
-
version:
|
24
|
-
key_file:
|
25
|
-
iv:
|
26
|
-
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
|
32
|
-
@app_name
|
31
|
+
@crypto_key = crypto_key
|
32
|
+
@app_name = app_name
|
33
33
|
@environment = environment
|
34
|
-
@file_name
|
35
|
-
@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,
|
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
|
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[
|
78
|
-
raise
|
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"] ||
|
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, **
|
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
|
@@ -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
|