symmetric-encryption 3.9.1 → 4.0.0.beta3
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 +72 -0
- data/bin/symmetric-encryption +5 -0
- data/lib/symmetric_encryption/cipher.rb +162 -419
- data/lib/symmetric_encryption/cli.rb +343 -0
- data/lib/symmetric_encryption/coerce.rb +5 -20
- data/lib/symmetric_encryption/config.rb +128 -50
- data/lib/symmetric_encryption/extensions/mongo_mapper/plugins/encrypted_key.rb +2 -2
- data/lib/symmetric_encryption/generator.rb +3 -2
- data/lib/symmetric_encryption/header.rb +260 -0
- data/lib/symmetric_encryption/key.rb +106 -0
- data/lib/symmetric_encryption/keystore/environment.rb +90 -0
- data/lib/symmetric_encryption/keystore/file.rb +102 -0
- data/lib/symmetric_encryption/keystore/memory.rb +53 -0
- data/lib/symmetric_encryption/keystore.rb +124 -0
- data/lib/symmetric_encryption/railtie.rb +5 -7
- data/lib/symmetric_encryption/reader.rb +74 -55
- data/lib/symmetric_encryption/rsa_key.rb +24 -0
- data/lib/symmetric_encryption/symmetric_encryption.rb +64 -102
- data/lib/symmetric_encryption/utils/re_encrypt_files.rb +140 -0
- data/lib/symmetric_encryption/version.rb +1 -1
- data/lib/symmetric_encryption/writer.rb +104 -117
- data/lib/symmetric_encryption.rb +9 -4
- data/test/active_record_test.rb +61 -40
- data/test/cipher_test.rb +179 -236
- data/test/config/symmetric-encryption.yml +140 -82
- data/test/header_test.rb +218 -0
- data/test/key_test.rb +231 -0
- data/test/keystore/environment_test.rb +119 -0
- data/test/keystore/file_test.rb +125 -0
- data/test/keystore_test.rb +59 -0
- data/test/mongoid_test.rb +13 -13
- data/test/reader_test.rb +52 -53
- data/test/symmetric_encryption_test.rb +50 -135
- data/test/test_db.sqlite3 +0 -0
- data/test/writer_test.rb +52 -31
- metadata +26 -14
- data/examples/symmetric-encryption.yml +0 -108
- data/lib/rails/generators/symmetric_encryption/config/config_generator.rb +0 -22
- data/lib/rails/generators/symmetric_encryption/config/templates/symmetric-encryption.yml +0 -50
- data/lib/rails/generators/symmetric_encryption/heroku_config/heroku_config_generator.rb +0 -20
- data/lib/rails/generators/symmetric_encryption/heroku_config/templates/symmetric-encryption.yml +0 -78
- data/lib/rails/generators/symmetric_encryption/new_keys/new_keys_generator.rb +0 -14
- data/lib/symmetric_encryption/key_encryption_key.rb +0 -32
- data/lib/symmetric_encryption/railties/symmetric_encryption.rake +0 -84
- data/lib/symmetric_encryption/utils/re_encrypt_config_files.rb +0 -82
@@ -1,188 +1,50 @@
|
|
1
1
|
require 'openssl'
|
2
2
|
module SymmetricEncryption
|
3
3
|
# Hold all information related to encryption keys
|
4
|
-
# as well as encrypt and decrypt data using those keys
|
4
|
+
# as well as encrypt and decrypt data using those keys.
|
5
5
|
#
|
6
6
|
# Cipher is thread safe so that the same instance can be called by multiple
|
7
|
-
# threads at the same time without needing an instance of Cipher per thread
|
7
|
+
# threads at the same time without needing an instance of Cipher per thread.
|
8
8
|
class Cipher
|
9
9
|
# Cipher to use for encryption and decryption
|
10
10
|
attr_accessor :cipher_name, :version, :iv, :always_add_header
|
11
|
-
attr_reader :
|
11
|
+
attr_reader :encoding
|
12
12
|
attr_writer :key
|
13
13
|
|
14
|
-
#
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
:
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
# Generate a new Symmetric Key pair
|
34
|
-
#
|
35
|
-
# Returns a hash containing a new random symmetric_key pair
|
36
|
-
# consisting of a :key and :iv.
|
37
|
-
# The cipher_name is also included for compatibility with the Cipher initializer
|
38
|
-
#
|
39
|
-
# Notes:
|
40
|
-
# * The key _must_ be properly secured
|
41
|
-
# * The iv can be stored in the clear and it is not necessary to encrypt it
|
42
|
-
def self.random_key_pair(cipher_name = 'aes-256-cbc')
|
43
|
-
openssl_cipher = ::OpenSSL::Cipher.new(cipher_name)
|
44
|
-
openssl_cipher.encrypt
|
45
|
-
|
46
|
-
{
|
47
|
-
key: openssl_cipher.random_key,
|
48
|
-
iv: openssl_cipher.random_iv,
|
49
|
-
cipher_name: cipher_name
|
50
|
-
}
|
14
|
+
# Returns [Cipher] from a cipher config instance.
|
15
|
+
def self.from_config(cipher_name: 'aes-256-cbc',
|
16
|
+
version: 0,
|
17
|
+
always_add_header: true,
|
18
|
+
encoding: :base64strict,
|
19
|
+
**config)
|
20
|
+
|
21
|
+
Key.migrate_config!(config)
|
22
|
+
key = Key.from_config(cipher_name: cipher_name, **config)
|
23
|
+
|
24
|
+
Cipher.new(
|
25
|
+
key: key.key,
|
26
|
+
iv: key.iv,
|
27
|
+
cipher_name: cipher_name,
|
28
|
+
version: version,
|
29
|
+
always_add_header: always_add_header,
|
30
|
+
encoding: encoding
|
31
|
+
)
|
51
32
|
end
|
52
33
|
|
53
|
-
#
|
54
|
-
# Overwrites key files for the current environment.
|
34
|
+
# Returns [SymmetricEncryption::Cipher] for encryption and decryption purposes.
|
55
35
|
#
|
56
|
-
# Parameters
|
57
|
-
#
|
58
|
-
#
|
59
|
-
# key from the private_rsa_key.
|
60
|
-
# Or,
|
61
|
-
# :encrypted_key
|
62
|
-
# Symmetric key encrypted using the public key from the private_rsa_key
|
63
|
-
# and then Base64 encoded
|
64
|
-
#
|
65
|
-
# Note:
|
66
|
-
# If :key_filename and :encrypted_key are not supplied then a new :key will be returned.
|
67
|
-
# :key is the Symmetric Key to use for encryption and decryption.
|
68
|
-
#
|
69
|
-
#
|
70
|
-
# :iv_filename
|
71
|
-
# Name of file containing symmetric key initialization vector
|
72
|
-
# encrypted using the public key from the private_rsa_key
|
73
|
-
# Deprecated: It is _not_ necessary to encrypt the initialization vector (IV)
|
74
|
-
# Or,
|
75
|
-
# :encrypted_iv
|
76
|
-
# Initialization vector encrypted using the public key from the private_rsa_key
|
77
|
-
# and then Base64 encoded
|
78
|
-
# Deprecated: It is _not_ necessary to encrypt the initialization vector (IV)
|
79
|
-
#
|
80
|
-
# Note:
|
81
|
-
# If :iv_filename and :encrypted_iv are not supplied then a new :iv will be returned.
|
82
|
-
# :key is the Initialization Vector to use with Symmetric Key.
|
83
|
-
#
|
84
|
-
#
|
85
|
-
# private_rsa_key [String]
|
86
|
-
# Key encryption key.
|
87
|
-
# To generate a new one: SymmetricEncryption::KeyEncryptionKey.generate
|
88
|
-
# Required if :key_filename, :encrypted_key, :iv_filename, or :encrypted_iv is supplied
|
89
|
-
#
|
90
|
-
# :cipher_name [String]
|
91
|
-
# Encryption Cipher to use.
|
92
|
-
# Default: aes-256-cbc
|
36
|
+
# Parameters:
|
37
|
+
# key [String]
|
38
|
+
# The Symmetric Key to use for encryption and decryption.
|
93
39
|
#
|
94
|
-
#
|
95
|
-
#
|
96
|
-
# Return as a base64 encoded string that does not include additional newlines
|
97
|
-
# This is the recommended format since newlines in the values to
|
98
|
-
# SQL queries are cumbersome. Also the newline reformatting is unnecessary
|
99
|
-
# It is not the default for backward compatibility
|
100
|
-
# :base64
|
101
|
-
# Return as a base64 encoded string
|
102
|
-
# :base16
|
103
|
-
# Return as a Hex encoded string
|
104
|
-
# :none
|
105
|
-
# Return as raw binary data string. Note: String can contain embedded nulls
|
106
|
-
# Default: :base64strict
|
107
|
-
def self.generate_random_keys(params = {})
|
108
|
-
params = params.dup
|
109
|
-
private_rsa_key = params.delete(:private_rsa_key)
|
110
|
-
cipher_name = params.delete(:cipher_name) || 'aes-256-cbc'
|
111
|
-
encoding = params.delete(:encoding) || :base64strict
|
112
|
-
unless private_rsa_key
|
113
|
-
[:key_filename, :encrypted_key, :iv_filename, :encrypted_iv].each do |key|
|
114
|
-
raise(SymmetricEncryption::ConfigError, "When :#{key} is supplied, :private_rsa_key is required.") if params.include?(key)
|
115
|
-
end
|
116
|
-
end
|
117
|
-
|
118
|
-
key_encryption_key = KeyEncryptionKey.new(private_rsa_key) if private_rsa_key
|
119
|
-
cipher_conf = {cipher_name: cipher_name, encoding: encoding}
|
120
|
-
|
121
|
-
key_pair = SymmetricEncryption::Cipher.random_key_pair(cipher_name)
|
122
|
-
key = key_pair[:key]
|
123
|
-
iv = key_pair[:iv]
|
124
|
-
|
125
|
-
if file_name = params.delete(:key_filename)
|
126
|
-
cipher_conf[:key_filename] = file_name
|
127
|
-
encrypted_key = key_encryption_key.encrypt(key)
|
128
|
-
write_to_file(file_name, encrypted_key)
|
129
|
-
elsif params.delete(:encrypted_key)
|
130
|
-
encrypted_key = key_encryption_key.encrypt(key)
|
131
|
-
cipher_conf[:encrypted_key] = SymmetricEncryption::Encoder[encoding].encode(encrypted_key)
|
132
|
-
else
|
133
|
-
params.delete(:key)
|
134
|
-
cipher_conf[:key] = SymmetricEncryption::Encoder[encoding].encode(key.to_s)
|
135
|
-
end
|
136
|
-
|
137
|
-
if file_name = params.delete(:iv_filename)
|
138
|
-
cipher_conf[:iv_filename] = file_name
|
139
|
-
encrypted_iv = key_encryption_key.encrypt(iv)
|
140
|
-
write_to_file(file_name, encrypted_iv)
|
141
|
-
elsif params.delete(:encrypted_iv)
|
142
|
-
encrypted_iv = key_encryption_key.encrypt(iv)
|
143
|
-
cipher_conf[:encrypted_iv] = SymmetricEncryption::Encoder[encoding].encode(encrypted_iv)
|
144
|
-
else
|
145
|
-
params.delete(:iv)
|
146
|
-
cipher_conf[:iv] = SymmetricEncryption::Encoder[encoding].encode(iv.to_s)
|
147
|
-
end
|
148
|
-
|
149
|
-
raise(ArgumentError, "SymmetricEncryption::Cipher Invalid options #{params.inspect}") if params.size > 0
|
150
|
-
cipher_conf
|
151
|
-
end
|
152
|
-
|
153
|
-
# Create a Symmetric::Key for encryption and decryption purposes
|
40
|
+
# iv [String]
|
41
|
+
# The Initialization Vector to use.
|
154
42
|
#
|
155
|
-
#
|
156
|
-
# :key [String]
|
157
|
-
# The Symmetric Key to use for encryption and decryption
|
158
|
-
# Or,
|
159
|
-
# :key_filename
|
160
|
-
# Name of file containing symmetric key encrypted using the public
|
161
|
-
# key from the private_rsa_key
|
162
|
-
# Or,
|
163
|
-
# :encrypted_key
|
164
|
-
# Symmetric key encrypted using the public key from the private_rsa_key
|
165
|
-
# and then Base64 encoded
|
166
|
-
#
|
167
|
-
# :iv [String]
|
168
|
-
# Optional. The Initialization Vector to use with Symmetric Key
|
169
|
-
# Highly Recommended as it is the input into the CBC algorithm
|
170
|
-
# Or,
|
171
|
-
# :iv_filename
|
172
|
-
# Name of file containing symmetric key initialization vector
|
173
|
-
# encrypted using the public key from the private_rsa_key
|
174
|
-
# Deprecated: It is _not_ necessary to encrypt the initialization vector (IV)
|
175
|
-
# Or,
|
176
|
-
# :encrypted_iv
|
177
|
-
# Initialization vector encrypted using the public key from the private_rsa_key
|
178
|
-
# and then Base64 encoded
|
179
|
-
# Deprecated: It is _not_ necessary to encrypt the initialization vector (IV)
|
180
|
-
#
|
181
|
-
# :cipher_name [String]
|
43
|
+
# cipher_name [String]
|
182
44
|
# Optional. Encryption Cipher to use
|
183
45
|
# Default: aes-256-cbc
|
184
46
|
#
|
185
|
-
#
|
47
|
+
# encoding [Symbol]
|
186
48
|
# :base64strict
|
187
49
|
# Return as a base64 encoded string that does not include additional newlines
|
188
50
|
# This is the recommended format since newlines in the values to
|
@@ -194,74 +56,48 @@ module SymmetricEncryption
|
|
194
56
|
# Return as a Hex encoded string
|
195
57
|
# :none
|
196
58
|
# Return as raw binary data string. Note: String can contain embedded nulls
|
197
|
-
# Default: :
|
198
|
-
# Recommended: :base64strict
|
59
|
+
# Default: :base64strict
|
199
60
|
#
|
200
|
-
#
|
61
|
+
# version [Fixnum]
|
201
62
|
# Optional. The version number of this encryption key
|
202
63
|
# Used by SymmetricEncryption to select the correct key when decrypting data
|
203
|
-
#
|
64
|
+
# Valid Range: 0..255
|
65
|
+
# Default: 1
|
204
66
|
#
|
205
|
-
#
|
67
|
+
# always_add_header [true|false]
|
206
68
|
# Whether to always include the header when encrypting data.
|
207
69
|
# ** Highly recommended to set this value to true **
|
208
70
|
# Increases the length of the encrypted data by a few bytes, but makes
|
209
71
|
# migration to a new key trivial
|
210
|
-
# Default:
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
@
|
220
|
-
@
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
raise(SymmetricEncryption::ConfigError, "When :#{key} is supplied, :private_rsa_key is required.") if params.include?(key)
|
227
|
-
end
|
228
|
-
end
|
229
|
-
|
230
|
-
key_encryption_key = KeyEncryptionKey.new(private_rsa_key) if private_rsa_key
|
231
|
-
@key =
|
232
|
-
if key = params.delete(:key)
|
233
|
-
key
|
234
|
-
elsif file_name = params.delete(:key_filename)
|
235
|
-
encrypted_key = self.class.read_from_file(file_name)
|
236
|
-
key_encryption_key.decrypt(encrypted_key)
|
237
|
-
elsif encrypted_key = params.delete(:encrypted_key)
|
238
|
-
binary = self.encoder.decode(encrypted_key)
|
239
|
-
key_encryption_key.decrypt(binary)
|
240
|
-
else
|
241
|
-
raise(ArgumentError, 'Missing mandatory parameter :key, :key_filename, or :encrypted_key')
|
242
|
-
end
|
243
|
-
|
244
|
-
@iv =
|
245
|
-
if iv = params.delete(:iv)
|
246
|
-
iv
|
247
|
-
elsif file_name = params.delete(:iv_filename)
|
248
|
-
encrypted_iv = self.class.read_from_file(file_name)
|
249
|
-
key_encryption_key.decrypt(encrypted_iv)
|
250
|
-
elsif encrypted_iv = params.delete(:encrypted_iv)
|
251
|
-
binary = self.encoder.decode(encrypted_iv)
|
252
|
-
key_encryption_key.decrypt(binary)
|
253
|
-
end
|
254
|
-
|
255
|
-
raise(ArgumentError, "Cipher version has a valid range of 0 to 255. #{@version} is too high, or negative") if (@version.to_i > 255) || (@version.to_i < 0)
|
256
|
-
raise(ArgumentError, "SymmetricEncryption::Cipher Invalid options #{params.inspect}") if params.size > 0
|
72
|
+
# Default: true
|
73
|
+
def initialize(key:,
|
74
|
+
iv: nil,
|
75
|
+
cipher_name: 'aes-256-cbc',
|
76
|
+
version: 0,
|
77
|
+
always_add_header: true,
|
78
|
+
encoding: :base64strict)
|
79
|
+
|
80
|
+
@key = key
|
81
|
+
@iv = iv
|
82
|
+
@cipher_name = cipher_name
|
83
|
+
self.encoding = encoding.to_sym
|
84
|
+
@version = version.to_i
|
85
|
+
@always_add_header = always_add_header
|
86
|
+
|
87
|
+
raise(ArgumentError, "Cipher version has a valid range of 0 to 255. #{@version} is too high, or negative") if (@version > 255) || (@version < 0)
|
257
88
|
end
|
258
89
|
|
259
90
|
# Change the encoding
|
260
91
|
def encoding=(encoding)
|
261
|
-
@encoder =
|
92
|
+
@encoder = nil
|
262
93
|
@encoding = encoding
|
263
94
|
end
|
264
95
|
|
96
|
+
# Returns [SymmetricEncryption::Encoder] the encoder to use for the current encoding.
|
97
|
+
def encoder
|
98
|
+
@encoder ||= SymmetricEncryption::Encoder[encoding]
|
99
|
+
end
|
100
|
+
|
265
101
|
# Encrypt and then encode a string
|
266
102
|
#
|
267
103
|
# Returns data encrypted and then encoded according to the encoding setting
|
@@ -278,31 +114,28 @@ module SymmetricEncryption
|
|
278
114
|
# random_iv [true|false]
|
279
115
|
# Whether the encypted value should use a random IV every time the
|
280
116
|
# field is encrypted.
|
281
|
-
#
|
282
|
-
#
|
283
|
-
#
|
284
|
-
#
|
285
|
-
# the
|
286
|
-
#
|
287
|
-
#
|
288
|
-
# Note: When random_iv is true it will add a 8 byte header, plus the bytes
|
289
|
-
# to store the random IV in every returned encrypted string, prior to the
|
290
|
-
# encoding if any.
|
117
|
+
# Notes:
|
118
|
+
# * Setting random_iv to true will result in a different encrypted output for
|
119
|
+
# the same input string.
|
120
|
+
# * It is recommended to set this to true, except if it will be used as a lookup key.
|
121
|
+
# * Only set to true if the field will never be used as a lookup key, since
|
122
|
+
# the encrypted value needs to be same every time in this case.
|
123
|
+
# * When random_iv is true it adds the random IV string to the header.
|
291
124
|
# Default: false
|
292
125
|
# Highly Recommended where feasible: true
|
293
126
|
#
|
294
127
|
# compress [true|false]
|
295
|
-
# Whether to compress str before encryption
|
296
|
-
# Should only be used for large strings since compression overhead and
|
297
|
-
# the overhead of adding the encryption header may exceed any benefits of
|
298
|
-
# compression
|
299
|
-
# Note: Adds a 6 byte header prior to encoding, only if :random_iv is false
|
128
|
+
# Whether to compress str before encryption.
|
300
129
|
# Default: false
|
301
|
-
|
130
|
+
# Notes:
|
131
|
+
# * Should only be used for large strings since compression overhead and
|
132
|
+
# the overhead of adding the encryption header may exceed any benefits of
|
133
|
+
# compression
|
134
|
+
def encrypt(str, random_iv: false, compress: false, header: always_add_header)
|
302
135
|
return if str.nil?
|
303
136
|
str = str.to_s
|
304
137
|
return str if str.empty?
|
305
|
-
encrypted = binary_encrypt(str, random_iv, compress)
|
138
|
+
encrypted = binary_encrypt(str, random_iv: random_iv, compress: compress, header: header)
|
306
139
|
self.encode(encrypted)
|
307
140
|
end
|
308
141
|
|
@@ -329,12 +162,12 @@ module SymmetricEncryption
|
|
329
162
|
|
330
163
|
return decoded if decoded.empty?
|
331
164
|
decrypted = binary_decrypt(decoded)
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
end
|
165
|
+
|
166
|
+
# Try to force result to UTF-8 encoding, but if it is not valid, force it back to Binary
|
167
|
+
unless decrypted.force_encoding(SymmetricEncryption::UTF8_ENCODING).valid_encoding?
|
168
|
+
decrypted.force_encoding(SymmetricEncryption::BINARY_ENCODING)
|
337
169
|
end
|
170
|
+
|
338
171
|
decrypted
|
339
172
|
end
|
340
173
|
|
@@ -362,166 +195,77 @@ module SymmetricEncryption
|
|
362
195
|
# Return a new random key using the configured cipher_name
|
363
196
|
# Useful for generating new symmetric keys
|
364
197
|
def random_key
|
365
|
-
::OpenSSL::Cipher
|
198
|
+
::OpenSSL::Cipher.new(cipher_name).random_key
|
366
199
|
end
|
367
200
|
|
368
|
-
#
|
369
|
-
|
370
|
-
|
201
|
+
# Return a new random IV using the configured cipher_name
|
202
|
+
# Useful for generating new symmetric keys
|
203
|
+
def random_iv
|
204
|
+
::OpenSSL::Cipher.new(cipher_name).random_iv
|
371
205
|
end
|
372
206
|
|
373
|
-
# Returns
|
374
|
-
|
375
|
-
|
376
|
-
return false if buffer.nil? || (buffer == '')
|
377
|
-
buffer.force_encoding(SymmetricEncryption::BINARY_ENCODING) if buffer.respond_to?(:force_encoding)
|
378
|
-
buffer.start_with?(MAGIC_HEADER)
|
207
|
+
# Returns the block size for the configured cipher_name
|
208
|
+
def block_size
|
209
|
+
::OpenSSL::Cipher.new(cipher_name).block_size
|
379
210
|
end
|
380
211
|
|
381
|
-
#
|
382
|
-
# Returns nil if no header is present
|
383
|
-
#
|
384
|
-
# The supplied buffer will be updated directly and its header will be
|
385
|
-
# stripped if present
|
212
|
+
# Advanced use only
|
386
213
|
#
|
387
|
-
#
|
388
|
-
# buffer
|
389
|
-
# String to extract the header from
|
214
|
+
# Returns a Binary encrypted string without applying Base64, or any other encoding.
|
390
215
|
#
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
# Header includes magic header and version byte
|
395
|
-
#
|
396
|
-
# The encryption header consists of:
|
397
|
-
# 4 Byte Magic Header Prefix: @Enc
|
398
|
-
# Followed by 2 Bytes (16 bits)
|
399
|
-
# Bit 0 through 7: The version of the cipher used to encrypt the header
|
400
|
-
# Bit 8 though 10: Reserved
|
401
|
-
# Bit 11: Whether the encrypted data is Binary (otherwise UTF8 text)
|
402
|
-
# Bit 12: Whether the Cipher Name is included
|
403
|
-
# Bit 13: Whether the Key is included
|
404
|
-
# Bit 14: Whether the IV is included
|
405
|
-
# Bit 15: Whether the data is compressed
|
406
|
-
# 2 Byte IV Length if included
|
407
|
-
# IV in binary form
|
408
|
-
# 2 Byte Key Length if included
|
409
|
-
# Key in binary form
|
410
|
-
# 2 Byte Cipher Name Length if included
|
411
|
-
# Cipher name it UTF8 text
|
412
|
-
|
413
|
-
# Remove header and extract flags
|
414
|
-
_, flags = buffer.slice!(0..MAGIC_HEADER_SIZE+1).unpack(MAGIC_HEADER_UNPACK)
|
415
|
-
compressed = (flags & 0b1000_0000_0000_0000) != 0
|
416
|
-
include_iv = (flags & 0b0100_0000_0000_0000) != 0
|
417
|
-
include_key = (flags & 0b0010_0000_0000_0000) != 0
|
418
|
-
include_cipher = (flags & 0b0001_0000_0000_0000) != 0
|
419
|
-
# Version of the key to use to decrypt the key if present,
|
420
|
-
# otherwise to decrypt the data following the header
|
421
|
-
version = flags & 0b0000_0000_1111_1111
|
422
|
-
decryption_cipher = SymmetricEncryption.cipher(version)
|
423
|
-
raise(SymmetricEncryption::CipherError, "Cipher with version:#{version.inspect} not found in any of the configured SymmetricEncryption ciphers") unless decryption_cipher
|
424
|
-
iv, key, cipher_name = nil
|
425
|
-
|
426
|
-
if include_iv
|
427
|
-
len = buffer.slice!(0..1).unpack('v').first
|
428
|
-
iv = buffer.slice!(0..len-1)
|
429
|
-
end
|
430
|
-
if include_key
|
431
|
-
len = buffer.slice!(0..1).unpack('v').first
|
432
|
-
key = decryption_cipher.binary_decrypt(buffer.slice!(0..len-1), false)
|
433
|
-
end
|
434
|
-
if include_cipher
|
435
|
-
len = buffer.slice!(0..1).unpack('v').first
|
436
|
-
cipher_name = buffer.slice!(0..len-1)
|
437
|
-
end
|
438
|
-
|
439
|
-
HeaderStruct.new(compressed, iv, key, cipher_name, version, decryption_cipher)
|
440
|
-
end
|
441
|
-
|
442
|
-
# Returns a magic header for this cipher instance that can be placed at
|
443
|
-
# the beginning of a file or stream to indicate how the data was encrypted
|
216
|
+
# str [String]
|
217
|
+
# String to be encrypted. If str is not a string, #to_s will be called on it
|
218
|
+
# to convert it to a string
|
444
219
|
#
|
445
|
-
#
|
446
|
-
#
|
447
|
-
#
|
220
|
+
# random_iv [true|false]
|
221
|
+
# Whether the encypted value should use a random IV every time the
|
222
|
+
# field is encrypted.
|
223
|
+
# Notes:
|
224
|
+
# * Setting random_iv to true will result in a different encrypted output for
|
225
|
+
# the same input string.
|
226
|
+
# * It is recommended to set this to true, except if it will be used as a lookup key.
|
227
|
+
# * Only set to true if the field will never be used as a lookup key, since
|
228
|
+
# the encrypted value needs to be same every time in this case.
|
229
|
+
# * When random_iv is true it adds the random IV string to the header.
|
448
230
|
# Default: false
|
231
|
+
# Highly Recommended where feasible: true
|
449
232
|
#
|
450
|
-
#
|
451
|
-
#
|
452
|
-
# Default:
|
453
|
-
#
|
454
|
-
#
|
455
|
-
#
|
456
|
-
#
|
457
|
-
# Default: nil : Exclude key from header
|
458
|
-
#
|
459
|
-
# cipher_name
|
460
|
-
# Includes the cipher_name used. For example 'aes-256-cbc'
|
461
|
-
# The cipher_name string to to put in the header
|
462
|
-
# Default: nil : Exclude cipher_name name from header
|
463
|
-
def self.build_header(version, compressed=false, iv=nil, key=nil, cipher_name=nil)
|
464
|
-
version ||= SymmetricEncryption.cipher.version
|
465
|
-
|
466
|
-
flags = version # Same as 0b0000_0000_0000_0000
|
467
|
-
|
468
|
-
# If the data is to be compressed before being encrypted, set the
|
469
|
-
# compressed bit in the flags word
|
470
|
-
flags |= 0b1000_0000_0000_0000 if compressed
|
471
|
-
flags |= 0b0100_0000_0000_0000 if iv
|
472
|
-
flags |= 0b0010_0000_0000_0000 if key
|
473
|
-
flags |= 0b0001_0000_0000_0000 if cipher_name
|
474
|
-
header = "#{MAGIC_HEADER}#{[flags].pack('v')}".force_encoding(SymmetricEncryption::BINARY_ENCODING)
|
475
|
-
if iv
|
476
|
-
header << [iv.length].pack('v')
|
477
|
-
header << iv
|
478
|
-
end
|
479
|
-
if key
|
480
|
-
encrypted = SymmetricEncryption.cipher(version).binary_encrypt(key, false, false, false)
|
481
|
-
header << [encrypted.length].pack('v').force_encoding(SymmetricEncryption::BINARY_ENCODING)
|
482
|
-
header << encrypted
|
483
|
-
end
|
484
|
-
if cipher_name
|
485
|
-
header << [cipher_name.length].pack('v')
|
486
|
-
header << cipher_name
|
487
|
-
end
|
488
|
-
header
|
489
|
-
end
|
490
|
-
|
491
|
-
# Advanced use only
|
492
|
-
#
|
493
|
-
# Returns a Binary encrypted string without applying any Base64, or other encoding
|
494
|
-
#
|
495
|
-
# add_header [nil|true|false]
|
496
|
-
# Whether to add a header to the encrypted string
|
497
|
-
# If not supplied it defaults to true if always_add_header || random_iv || compress
|
498
|
-
# Default: nil
|
233
|
+
# compress [true|false]
|
234
|
+
# Whether to compress str before encryption.
|
235
|
+
# Default: false
|
236
|
+
# Notes:
|
237
|
+
# * Should only be used for large strings since compression overhead and
|
238
|
+
# the overhead of adding the encryption header may exceed any benefits of
|
239
|
+
# compression
|
499
240
|
#
|
500
|
-
#
|
501
|
-
#
|
241
|
+
# header [true|false]
|
242
|
+
# Whether to add a header to the encrypted string.
|
243
|
+
# Default: `always_add_header`
|
502
244
|
#
|
503
|
-
# See #encrypt to encrypt and encode the result as a string
|
504
|
-
def binary_encrypt(str, random_iv
|
245
|
+
# See #encrypt to encrypt and encode the result as a string.
|
246
|
+
def binary_encrypt(str, random_iv: false, compress: false, header: always_add_header)
|
505
247
|
return if str.nil?
|
506
248
|
string = str.to_s
|
507
249
|
return string if string.empty?
|
508
250
|
|
509
|
-
#
|
510
|
-
|
511
|
-
|
251
|
+
# Header required when adding a random_iv or compressing
|
252
|
+
header = Header.new(version: version, compress: compress) if (header == true) || random_iv || compress
|
253
|
+
|
254
|
+
# Creates a new OpenSSL::Cipher with every call so that this call is thread-safe.
|
255
|
+
openssl_cipher = ::OpenSSL::Cipher.new(cipher_name)
|
512
256
|
openssl_cipher.encrypt
|
513
257
|
openssl_cipher.key = @key
|
514
|
-
|
515
|
-
result
|
516
|
-
if
|
517
|
-
|
518
|
-
|
519
|
-
|
520
|
-
|
521
|
-
|
522
|
-
|
258
|
+
|
259
|
+
result =
|
260
|
+
if header
|
261
|
+
if random_iv
|
262
|
+
openssl_cipher.iv = header.iv = openssl_cipher.random_iv
|
263
|
+
elsif self.iv
|
264
|
+
openssl_cipher.iv = self.iv
|
265
|
+
end
|
266
|
+
header.to_s + openssl_cipher.update(compress ? Zlib::Deflate.deflate(string) : string)
|
523
267
|
else
|
524
|
-
openssl_cipher.iv =
|
268
|
+
openssl_cipher.iv = iv if iv
|
525
269
|
openssl_cipher.update(string)
|
526
270
|
end
|
527
271
|
result << openssl_cipher.final
|
@@ -542,7 +286,7 @@ module SymmetricEncryption
|
|
542
286
|
# encrypted_string [String]
|
543
287
|
# Binary encrypted string to decrypt
|
544
288
|
#
|
545
|
-
# header [
|
289
|
+
# header [SymmetricEncryption::Header]
|
546
290
|
# Optional header for the supplied encrypted_string
|
547
291
|
#
|
548
292
|
# Reads the 'magic' header if present for key, iv, cipher_name and compression
|
@@ -556,57 +300,56 @@ module SymmetricEncryption
|
|
556
300
|
# Note:
|
557
301
|
# When a string is encrypted and the header is used, its decrypted form
|
558
302
|
# is automatically set to the same UTF-8 or Binary encoding
|
559
|
-
def binary_decrypt(encrypted_string, header
|
303
|
+
def binary_decrypt(encrypted_string, header: Header.new)
|
560
304
|
return if encrypted_string.nil?
|
561
305
|
str = encrypted_string.to_s
|
562
|
-
str.force_encoding(SymmetricEncryption::BINARY_ENCODING)
|
306
|
+
str.force_encoding(SymmetricEncryption::BINARY_ENCODING)
|
563
307
|
return str if str.empty?
|
564
308
|
|
565
|
-
|
566
|
-
|
567
|
-
|
568
|
-
|
569
|
-
|
570
|
-
|
571
|
-
|
572
|
-
|
573
|
-
openssl_cipher.iv = iv if iv
|
574
|
-
result = openssl_cipher.update(str)
|
575
|
-
result << openssl_cipher.final
|
576
|
-
header.compressed ? Zlib::Inflate.inflate(result) : result
|
577
|
-
else
|
578
|
-
openssl_cipher = ::OpenSSL::Cipher.new(self.cipher_name)
|
579
|
-
openssl_cipher.decrypt
|
580
|
-
openssl_cipher.key = @key
|
581
|
-
openssl_cipher.iv = @iv if @iv
|
582
|
-
result = openssl_cipher.update(str)
|
583
|
-
result << openssl_cipher.final
|
309
|
+
offset = header.parse(str)
|
310
|
+
data = offset > 0 ? str[offset..-1] : str
|
311
|
+
|
312
|
+
openssl_cipher = ::OpenSSL::Cipher.new(header.cipher_name || cipher_name)
|
313
|
+
openssl_cipher.decrypt
|
314
|
+
openssl_cipher.key = header.key || @key
|
315
|
+
if iv = (header.iv || @iv)
|
316
|
+
openssl_cipher.iv = iv
|
584
317
|
end
|
318
|
+
result = openssl_cipher.update(data)
|
319
|
+
result << openssl_cipher.final
|
320
|
+
header.compressed? ? Zlib::Inflate.inflate(result) : result
|
321
|
+
end
|
322
|
+
|
323
|
+
# Returns the magic header after applying the encoding in this cipher
|
324
|
+
def encoded_magic_header
|
325
|
+
@encoded_magic_header ||= encoder.encode(SymmetricEncryption::Header::MAGIC_HEADER).gsub('=', '').strip
|
585
326
|
end
|
586
327
|
|
587
328
|
# Returns [String] object represented as a string, filtering out the key
|
588
329
|
def inspect
|
589
|
-
"#<#{self.class}:0x#{self.__id__.to_s(16)} @key=\"[FILTERED]\" @iv=#{iv.inspect} @cipher_name=#{cipher_name.inspect}, @version=#{version.inspect}, @encoding=#{encoding.inspect}, @always_add_header=#{always_add_header.inspect}"
|
330
|
+
"#<#{self.class}:0x#{self.__id__.to_s(16)} @key=\"[FILTERED]\" @iv=#{iv.inspect} @cipher_name=#{cipher_name.inspect}, @version=#{version.inspect}, @encoding=#{encoding.inspect}, @always_add_header=#{always_add_header.inspect}>"
|
590
331
|
end
|
591
332
|
|
592
|
-
|
593
|
-
|
594
|
-
|
333
|
+
# DEPRECATED
|
334
|
+
def self.has_header?(buffer)
|
335
|
+
SymmetricEncryption::Header.present?(buffer)
|
336
|
+
end
|
595
337
|
|
596
|
-
#
|
597
|
-
def self.
|
598
|
-
|
599
|
-
|
600
|
-
puts "\nSymmetric Encryption key file: '#{file_name}' not found or readable."
|
601
|
-
puts "To generate the keys for the first time run: bin/rails generate symmetric_encryption:new_keys production\n\n"
|
602
|
-
raise(exc)
|
338
|
+
# DEPRECATED
|
339
|
+
def self.parse_header!(buffer)
|
340
|
+
header = SymmetricEncryption::Header.new
|
341
|
+
header.parse!(buffer) ? header : nil
|
603
342
|
end
|
604
343
|
|
605
|
-
#
|
606
|
-
def self.
|
607
|
-
|
608
|
-
|
344
|
+
# DEPRECATED
|
345
|
+
def self.build_header(version, compress = false, iv = nil, key = nil, cipher_name = nil)
|
346
|
+
h = Header.new(version: version, compress: compress, iv: iv, key: key, cipher_name: cipher_name)
|
347
|
+
h.to_s
|
609
348
|
end
|
610
349
|
|
350
|
+
private
|
351
|
+
|
352
|
+
attr_reader :key
|
353
|
+
|
611
354
|
end
|
612
355
|
end
|