symmetric-encryption 4.1.2 → 4.5.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/active_record/attr_encrypted.rb +129 -0
- 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 +76 -58
- data/lib/symmetric_encryption/coerce.rb +3 -3
- data/lib/symmetric_encryption/config.rb +37 -28
- data/lib/symmetric_encryption/core.rb +35 -0
- 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 +14 -32
- data/lib/symmetric_encryption/keystore/environment.rb +5 -5
- data/lib/symmetric_encryption/keystore/file.rb +34 -17
- data/lib/symmetric_encryption/keystore/gcp.rb +90 -0
- 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 -22
- data/lib/symmetric_encryption/railtie.rb +14 -13
- data/lib/symmetric_encryption/{extensions/mongoid/encrypted.rb → 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 +45 -0
- 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 +19 -49
- metadata +14 -13
- data/lib/symmetric_encryption/extensions/active_record/base.rb +0 -110
- data/lib/symmetric_encryption/extensions/mongo_mapper/plugins/encrypted_key.rb +0 -41
data/lib/symmetric_encryption/{extensions/mongoid/encrypted.rb → railties/mongoid_encrypted.rb}
RENAMED
@@ -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
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module SymmetricEncryption
|
2
|
+
module Utils
|
3
|
+
module Files
|
4
|
+
private
|
5
|
+
|
6
|
+
attr_reader :file_name
|
7
|
+
|
8
|
+
def read_file_and_decode(file_name)
|
9
|
+
raise(SymmetricEncryption::ConfigError, "file_name is mandatory for each key_file entry") unless file_name
|
10
|
+
|
11
|
+
raise(SymmetricEncryption::ConfigError, "File #{file_name} could not be found") unless ::File.exist?(file_name)
|
12
|
+
|
13
|
+
# TODO: Validate that file is not globally readable.
|
14
|
+
decode64(read_from_file(file_name))
|
15
|
+
end
|
16
|
+
|
17
|
+
def write_encoded_to_file(file_name, encrypted_data_key)
|
18
|
+
write_to_file(file_name, encode64(encrypted_data_key))
|
19
|
+
end
|
20
|
+
|
21
|
+
def encode64(data)
|
22
|
+
Base64.strict_encode64(data)
|
23
|
+
end
|
24
|
+
|
25
|
+
def decode64(data)
|
26
|
+
Base64.strict_decode64(data)
|
27
|
+
end
|
28
|
+
|
29
|
+
# Write to the supplied file_name, backing up the existing file if present
|
30
|
+
def write_to_file(file_name, data)
|
31
|
+
key_path = ::File.dirname(file_name)
|
32
|
+
::FileUtils.mkdir_p(key_path) unless ::File.directory?(key_path)
|
33
|
+
::File.rename(file_name, "#{file_name}.#{Time.now.to_i}") if ::File.exist?(file_name)
|
34
|
+
::File.open(file_name, "wb", 0o600) { |file| file.write(data) }
|
35
|
+
end
|
36
|
+
|
37
|
+
# Read from the file, raising an exception if it is not found
|
38
|
+
def read_from_file(file_name)
|
39
|
+
::File.open(file_name, "rb", &:read)
|
40
|
+
rescue Errno::ENOENT
|
41
|
+
raise(SymmetricEncryption::ConfigError, "Symmetric Encryption key file: '#{file_name}' not found or readable")
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -55,26 +55,26 @@ module SymmetricEncryption
|
|
55
55
|
lines = File.read(file_name)
|
56
56
|
hits, output_lines = re_encrypt_lines(lines)
|
57
57
|
|
58
|
-
File.open(file_name,
|
58
|
+
File.open(file_name, "wb") { |file| file.write(output_lines) } if hits.positive?
|
59
59
|
hits
|
60
60
|
end
|
61
61
|
|
62
62
|
# Replaces instances of encrypted data within lines of text with re-encrypted values
|
63
63
|
def re_encrypt_lines(lines)
|
64
64
|
hits = 0
|
65
|
-
output_lines =
|
65
|
+
output_lines = ""
|
66
66
|
r = regexp
|
67
67
|
lines.each_line do |line|
|
68
68
|
line.force_encoding(SymmetricEncryption::UTF8_ENCODING)
|
69
69
|
output_lines <<
|
70
70
|
if line.valid_encoding? && (result = line.match(r))
|
71
|
-
encrypted
|
72
|
-
new_value
|
73
|
-
if new_value
|
71
|
+
encrypted = result[0]
|
72
|
+
new_value = re_encrypt(encrypted)
|
73
|
+
if new_value == encrypted
|
74
|
+
line
|
75
|
+
else
|
74
76
|
hits += 1
|
75
77
|
line.gsub(encrypted, new_value)
|
76
|
-
else
|
77
|
-
line
|
78
78
|
end
|
79
79
|
else
|
80
80
|
line
|
@@ -117,8 +117,8 @@ module SymmetricEncryption
|
|
117
117
|
begin
|
118
118
|
count = re_encrypt_contents(file_name)
|
119
119
|
puts "Re-encrypted #{count} encrypted value(s) in: #{file_name}" if count.positive?
|
120
|
-
rescue StandardError =>
|
121
|
-
puts "Failed re-encrypting the file contents of: #{file_name}. #{
|
120
|
+
rescue StandardError => e
|
121
|
+
puts "Failed re-encrypting the file contents of: #{file_name}. #{e.class.name}: #{e.message}"
|
122
122
|
end
|
123
123
|
end
|
124
124
|
end
|
@@ -127,13 +127,13 @@ module SymmetricEncryption
|
|
127
127
|
private
|
128
128
|
|
129
129
|
def regexp
|
130
|
-
@regexp ||=
|
130
|
+
@regexp ||= %r{#{SymmetricEncryption.cipher.encoded_magic_header}([A-Za-z0-9+/]+[=\\n]*)}
|
131
131
|
end
|
132
132
|
|
133
133
|
# Returns [Integer] encrypted file key version.
|
134
134
|
# Returns [nil] if the file is not encrypted or does not have a header.
|
135
135
|
def encrypted_file_version(file_name)
|
136
|
-
::File.open(file_name,
|
136
|
+
::File.open(file_name, "rb") do |file|
|
137
137
|
reader = SymmetricEncryption::Reader.new(file)
|
138
138
|
reader.version if reader.header_present?
|
139
139
|
end
|
@@ -1,4 +1,4 @@
|
|
1
|
-
require
|
1
|
+
require "openssl"
|
2
2
|
|
3
3
|
module SymmetricEncryption
|
4
4
|
# Write to encrypted files and other IO streams.
|
@@ -49,10 +49,10 @@ module SymmetricEncryption
|
|
49
49
|
# end
|
50
50
|
def self.open(file_name_or_stream, compress: nil, **args)
|
51
51
|
if file_name_or_stream.is_a?(String)
|
52
|
-
file_name_or_stream = ::File.open(file_name_or_stream,
|
52
|
+
file_name_or_stream = ::File.open(file_name_or_stream, "wb")
|
53
53
|
compress = !(/\.(zip|gz|gzip|xls.|)\z/i === file_name_or_stream) if compress.nil?
|
54
|
-
|
55
|
-
compress = true
|
54
|
+
elsif compress.nil?
|
55
|
+
compress = true
|
56
56
|
end
|
57
57
|
|
58
58
|
begin
|
@@ -97,15 +97,22 @@ module SymmetricEncryption
|
|
97
97
|
def initialize(ios, version: nil, cipher_name: nil, header: true, random_key: true, random_iv: true, compress: false)
|
98
98
|
# Compress is only used at this point for setting the flag in the header
|
99
99
|
@ios = ios
|
100
|
-
raise(ArgumentError,
|
101
|
-
|
100
|
+
raise(ArgumentError, "When :random_key is true, :random_iv must also be true") if random_key && !random_iv
|
101
|
+
if cipher_name && !random_key && !random_iv
|
102
|
+
raise(ArgumentError, "Cannot supply a :cipher_name unless both :random_key and :random_iv are true")
|
103
|
+
end
|
102
104
|
|
103
105
|
# Cipher to encrypt the random_key, or the entire file
|
104
106
|
cipher = SymmetricEncryption.cipher(version)
|
105
|
-
|
107
|
+
unless cipher
|
108
|
+
raise(SymmetricEncryption::CipherError,
|
109
|
+
"Cipher with version:#{version} not found in any of the configured SymmetricEncryption ciphers")
|
110
|
+
end
|
106
111
|
|
107
112
|
# Force header if compressed or using random iv, key
|
108
|
-
|
113
|
+
if (header == true) || compress || random_key || random_iv
|
114
|
+
header = Header.new(version: cipher.version, compress: compress, cipher_name: cipher_name)
|
115
|
+
end
|
109
116
|
|
110
117
|
@stream_cipher = ::OpenSSL::Cipher.new(cipher_name || cipher.cipher_name)
|
111
118
|
@stream_cipher.encrypt
|
@@ -158,8 +165,8 @@ module SymmetricEncryption
|
|
158
165
|
def write(data)
|
159
166
|
return unless data
|
160
167
|
|
161
|
-
bytes
|
162
|
-
@size
|
168
|
+
bytes = data.to_s
|
169
|
+
@size += bytes.size
|
163
170
|
partial = @stream_cipher.update(bytes)
|
164
171
|
@ios.write(partial) unless partial.empty?
|
165
172
|
data.length
|
@@ -168,9 +175,9 @@ module SymmetricEncryption
|
|
168
175
|
def write(data)
|
169
176
|
return unless data
|
170
177
|
|
171
|
-
bytes
|
172
|
-
@size
|
173
|
-
partial = @stream_cipher.update(bytes, @cipher_buffer ||=
|
178
|
+
bytes = data.to_s
|
179
|
+
@size += bytes.size
|
180
|
+
partial = @stream_cipher.update(bytes, @cipher_buffer ||= "".b)
|
174
181
|
@ios.write(partial) unless partial.empty?
|
175
182
|
data.length
|
176
183
|
end
|
data/lib/symmetric_encryption.rb
CHANGED
@@ -1,61 +1,31 @@
|
|
1
|
-
|
2
|
-
require 'zlib'
|
3
|
-
# Used to coerce data types between string and their actual types
|
4
|
-
require 'coercible'
|
5
|
-
|
6
|
-
require 'symmetric_encryption/version'
|
7
|
-
require 'symmetric_encryption/cipher'
|
8
|
-
require 'symmetric_encryption/symmetric_encryption'
|
9
|
-
require 'symmetric_encryption/exception'
|
10
|
-
|
11
|
-
# @formatter:off
|
12
|
-
module SymmetricEncryption
|
13
|
-
autoload :Coerce, 'symmetric_encryption/coerce'
|
14
|
-
autoload :Config, 'symmetric_encryption/config'
|
15
|
-
autoload :Encoder, 'symmetric_encryption/encoder'
|
16
|
-
autoload :Generator, 'symmetric_encryption/generator'
|
17
|
-
autoload :Header, 'symmetric_encryption/header'
|
18
|
-
autoload :Key, 'symmetric_encryption/key'
|
19
|
-
autoload :Reader, 'symmetric_encryption/reader'
|
20
|
-
autoload :RSAKey, 'symmetric_encryption/rsa_key'
|
21
|
-
autoload :Writer, 'symmetric_encryption/writer'
|
22
|
-
autoload :CLI, 'symmetric_encryption/cli'
|
23
|
-
autoload :Keystore, 'symmetric_encryption/keystore'
|
24
|
-
module Utils
|
25
|
-
autoload :Aws, 'symmetric_encryption/utils/aws'
|
26
|
-
autoload :ReEncryptFiles, 'symmetric_encryption/utils/re_encrypt_files'
|
27
|
-
end
|
28
|
-
end
|
29
|
-
# @formatter:on
|
1
|
+
require "symmetric_encryption/core"
|
30
2
|
|
31
3
|
# Add extensions. Gems are no longer order dependent.
|
32
4
|
begin
|
33
|
-
require
|
34
|
-
require
|
5
|
+
require "rails"
|
6
|
+
require "symmetric_encryption/railtie"
|
35
7
|
rescue LoadError
|
36
8
|
end
|
37
9
|
|
38
10
|
begin
|
39
|
-
require
|
40
|
-
|
41
|
-
|
42
|
-
|
11
|
+
require "active_support"
|
12
|
+
ActiveSupport.on_load(:active_record) do
|
13
|
+
require "symmetric_encryption/active_record/attr_encrypted"
|
14
|
+
require "symmetric_encryption/railties/symmetric_encryption_validator"
|
43
15
|
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
rescue LoadError
|
48
|
-
end
|
16
|
+
if ActiveRecord.version >= Gem::Version.new("5.0.0")
|
17
|
+
ActiveRecord::Type.register(:encrypted, SymmetricEncryption::ActiveRecord::EncryptedAttribute)
|
18
|
+
end
|
49
19
|
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
end
|
20
|
+
# Remove old way of defining attributes with Rails 7 since it conflicts with the method names.
|
21
|
+
if ActiveRecord.version <= Gem::Version.new("7.0.0")
|
22
|
+
ActiveRecord::Base.include(SymmetricEncryption::ActiveRecord::AttrEncrypted)
|
23
|
+
end
|
24
|
+
end
|
55
25
|
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
26
|
+
ActiveSupport.on_load(:mongoid) do
|
27
|
+
require "symmetric_encryption/railties/mongoid_encrypted"
|
28
|
+
require "symmetric_encryption/railties/symmetric_encryption_validator"
|
29
|
+
end
|
60
30
|
rescue LoadError
|
61
31
|
end
|