symmetric-encryption 3.9.1 → 4.0.0.beta3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +72 -0
  3. data/bin/symmetric-encryption +5 -0
  4. data/lib/symmetric_encryption/cipher.rb +162 -419
  5. data/lib/symmetric_encryption/cli.rb +343 -0
  6. data/lib/symmetric_encryption/coerce.rb +5 -20
  7. data/lib/symmetric_encryption/config.rb +128 -50
  8. data/lib/symmetric_encryption/extensions/mongo_mapper/plugins/encrypted_key.rb +2 -2
  9. data/lib/symmetric_encryption/generator.rb +3 -2
  10. data/lib/symmetric_encryption/header.rb +260 -0
  11. data/lib/symmetric_encryption/key.rb +106 -0
  12. data/lib/symmetric_encryption/keystore/environment.rb +90 -0
  13. data/lib/symmetric_encryption/keystore/file.rb +102 -0
  14. data/lib/symmetric_encryption/keystore/memory.rb +53 -0
  15. data/lib/symmetric_encryption/keystore.rb +124 -0
  16. data/lib/symmetric_encryption/railtie.rb +5 -7
  17. data/lib/symmetric_encryption/reader.rb +74 -55
  18. data/lib/symmetric_encryption/rsa_key.rb +24 -0
  19. data/lib/symmetric_encryption/symmetric_encryption.rb +64 -102
  20. data/lib/symmetric_encryption/utils/re_encrypt_files.rb +140 -0
  21. data/lib/symmetric_encryption/version.rb +1 -1
  22. data/lib/symmetric_encryption/writer.rb +104 -117
  23. data/lib/symmetric_encryption.rb +9 -4
  24. data/test/active_record_test.rb +61 -40
  25. data/test/cipher_test.rb +179 -236
  26. data/test/config/symmetric-encryption.yml +140 -82
  27. data/test/header_test.rb +218 -0
  28. data/test/key_test.rb +231 -0
  29. data/test/keystore/environment_test.rb +119 -0
  30. data/test/keystore/file_test.rb +125 -0
  31. data/test/keystore_test.rb +59 -0
  32. data/test/mongoid_test.rb +13 -13
  33. data/test/reader_test.rb +52 -53
  34. data/test/symmetric_encryption_test.rb +50 -135
  35. data/test/test_db.sqlite3 +0 -0
  36. data/test/writer_test.rb +52 -31
  37. metadata +26 -14
  38. data/examples/symmetric-encryption.yml +0 -108
  39. data/lib/rails/generators/symmetric_encryption/config/config_generator.rb +0 -22
  40. data/lib/rails/generators/symmetric_encryption/config/templates/symmetric-encryption.yml +0 -50
  41. data/lib/rails/generators/symmetric_encryption/heroku_config/heroku_config_generator.rb +0 -20
  42. data/lib/rails/generators/symmetric_encryption/heroku_config/templates/symmetric-encryption.yml +0 -78
  43. data/lib/rails/generators/symmetric_encryption/new_keys/new_keys_generator.rb +0 -14
  44. data/lib/symmetric_encryption/key_encryption_key.rb +0 -32
  45. data/lib/symmetric_encryption/railties/symmetric_encryption.rake +0 -84
  46. data/lib/symmetric_encryption/utils/re_encrypt_config_files.rb +0 -82
@@ -0,0 +1,260 @@
1
+ module SymmetricEncryption
2
+ # Defines the Header Structure returned when parsing the header.
3
+ #
4
+ # Note:
5
+ # * Header only works against binary encrypted data that has not been decoded.
6
+ # * Decode data first before trying to extract its header.
7
+ # * Decoding is not required when encoding is set to `:none`.
8
+ class Header
9
+ # Encrypted data includes this header prior to encoding when
10
+ # `always_add_header` is true.
11
+ MAGIC_HEADER = '@EnC'.force_encoding(SymmetricEncryption::BINARY_ENCODING)
12
+ MAGIC_HEADER_SIZE = MAGIC_HEADER.size
13
+
14
+ # [true|false] Whether to compress the data before encryption.
15
+ # If supplied in the header.
16
+ attr_accessor :compress
17
+
18
+ # [String] IV used to encrypt the data.
19
+ # If supplied in the header.
20
+ attr_accessor :iv
21
+
22
+ # [String] Key used to encrypt the data.
23
+ # If supplied in the header.
24
+ attr_accessor :key
25
+
26
+ # [String] Name of the cipher used.
27
+ attr_accessor :cipher_name
28
+
29
+ # [Integer] Version of the cipher used.
30
+ attr_reader :version
31
+
32
+ # [String] Binary auth tag used to encrypt the data.
33
+ # Usually 16 bytes.
34
+ # Present when using an authenticated encryption mode.
35
+ attr_reader :auth_tag
36
+
37
+ # Returns whether the supplied buffer starts with a symmetric_encryption header
38
+ # Note: The encoding of the supplied buffer is forced to binary if not already binary
39
+ def self.present?(buffer)
40
+ return false if buffer.nil? || (buffer == '')
41
+ buffer.force_encoding(SymmetricEncryption::BINARY_ENCODING)
42
+ buffer.start_with?(MAGIC_HEADER)
43
+ end
44
+
45
+ # Returns a magic header for this cipher instance that can be placed at
46
+ # the beginning of a file or stream to indicate how the data was encrypted
47
+ #
48
+ # Parameters
49
+ # compress [true|false]
50
+ # Whether the data should be compressed before encryption.
51
+ # Default: false
52
+ #
53
+ # iv [String]
54
+ # The iv to to put in the header
55
+ # Default: nil : Exclude from header
56
+ #
57
+ # key [String]
58
+ # The key to to put in the header.
59
+ # The key is encrypted using the global encryption key
60
+ # Default: nil : Exclude key from header
61
+ #
62
+ # version: [Integer (0..255)]
63
+ # Version of the global cipher used to encrypt the data,
64
+ # or the encryption key if supplied.
65
+ # default: The current global encryption cipher version.
66
+ #
67
+ # cipher_name [String]
68
+ # The cipher_name to be used for encrypting the data portion.
69
+ # For example 'aes-256-cbc'
70
+ # `key` if supplied is encrypted with the cipher name based on the cipher version in this header.
71
+ # Intended for use when encrypting large files with a different cipher to the global one.
72
+ # Default: nil : Exclude cipher_name name from header
73
+ def initialize(version: SymmetricEncryption.cipher.version,
74
+ compress: false,
75
+ iv: nil,
76
+ key: nil,
77
+ cipher_name: nil,
78
+ auth_tag: nil)
79
+
80
+ @version = version
81
+ @compress = compress
82
+ @iv = iv
83
+ @key = key
84
+ @cipher_name = cipher_name
85
+ @auth_tag = auth_tag
86
+ end
87
+
88
+ # Returns [SymmetricEncryption::Cipher] the cipher used to decrypt or encrypt the key
89
+ # specified in this header, if supplied.
90
+ def cipher
91
+ @cipher ||= SymmetricEncryption.cipher(version)
92
+ end
93
+
94
+ def version=(version)
95
+ @version = version
96
+ @cipher = nil
97
+ end
98
+
99
+ def compressed?
100
+ @compress
101
+ end
102
+
103
+ # Returns [String] the encrypted data without header
104
+ # Returns nil if no header is present
105
+ #
106
+ # The supplied buffer will be updated directly and
107
+ # its header will be stripped if present.
108
+ #
109
+ # Parameters
110
+ # buffer
111
+ # String to extract the header from
112
+ def parse!(buffer)
113
+ offset = parse(buffer)
114
+ return if offset == 0
115
+ buffer.slice!(0..offset - 1)
116
+ buffer
117
+ end
118
+
119
+ # Returns [Integer] the offset within the buffer of the data after the header has been read.
120
+ #
121
+ # Returns 0 if no header is present
122
+ def parse(buffer, offset = 0)
123
+ return 0 if buffer.nil? || (buffer == '') || (buffer.length <= MAGIC_HEADER_SIZE + 2)
124
+
125
+ # Symmetric Encryption Header
126
+ #
127
+ # Consists of:
128
+ # 4 Bytes: Magic Header Prefix: @Enc
129
+ # 1 Byte: The version of the cipher used to encrypt the header.
130
+ # 1 Byte: Flags:
131
+ # Bit 1: Whether the data is compressed
132
+ # Bit 2: Whether the IV is included
133
+ # Bit 3: Whether the Key is included
134
+ # Bit 4: Whether the Cipher Name is included
135
+ # Bit 5: Future use
136
+ # Bit 6: Future use
137
+ # Bit 7: Future use
138
+ # Bit 8: Future use
139
+ # 2 Bytes: IV Length (little endian), if included.
140
+ # IV in binary form.
141
+ # 2 Bytes: Key Length (little endian), if included.
142
+ # Key in binary form
143
+ # 2 Bytes: Cipher Name Length (little endian), if included.
144
+ # Cipher name it UTF8 text
145
+
146
+ buffer.force_encoding(SymmetricEncryption::BINARY_ENCODING)
147
+ header = buffer.byteslice(offset, MAGIC_HEADER_SIZE)
148
+ return 0 unless header == MAGIC_HEADER
149
+
150
+ offset += MAGIC_HEADER_SIZE
151
+
152
+ # Remove header and extract flags
153
+ self.version = buffer.getbyte(offset)
154
+ offset += 1
155
+
156
+ raise(SymmetricEncryption::CipherError, "Cipher with version:#{version.inspect} not found in any of the configured SymmetricEncryption ciphers") unless cipher
157
+ flags = buffer.getbyte(offset)
158
+ offset += 1
159
+
160
+ self.compress = (flags & FLAG_COMPRESSED) != 0
161
+
162
+ if (flags & FLAG_IV) != 0
163
+ self.iv, offset = read_string(buffer, offset)
164
+ else
165
+ self.iv = nil
166
+ end
167
+
168
+ if (flags & FLAG_KEY) != 0
169
+ encrypted_key, offset = read_string(buffer, offset)
170
+ self.key = cipher.binary_decrypt(encrypted_key)
171
+ else
172
+ self.key = nil
173
+ end
174
+
175
+ if (flags & FLAG_CIPHER_NAME) != 0
176
+ self.cipher_name, offset = read_string(buffer, offset)
177
+ else
178
+ self.cipher_name = nil
179
+ end
180
+
181
+ if (flags & FLAG_AUTH_TAG) != 0
182
+ self.auth_tag, offset = read_string(buffer, offset)
183
+ else
184
+ self.auth_tag = nil
185
+ end
186
+
187
+ offset
188
+ end
189
+
190
+ # Returns [String] this header as a string
191
+ def to_s
192
+ flags = 0
193
+ flags |= FLAG_COMPRESSED if compressed?
194
+ flags |= FLAG_IV if iv
195
+ flags |= FLAG_KEY if key
196
+ flags |= FLAG_CIPHER_NAME if cipher_name
197
+ flags |= FLAG_AUTH_TAG if auth_tag
198
+
199
+ header = "#{MAGIC_HEADER}#{version.chr(SymmetricEncryption::BINARY_ENCODING)}#{flags.chr(SymmetricEncryption::BINARY_ENCODING)}"
200
+
201
+ if iv
202
+ header << [iv.length].pack('v')
203
+ header << iv
204
+ end
205
+
206
+ if key
207
+ encrypted = cipher.binary_encrypt(key, header: false)
208
+ header << [encrypted.length].pack('v')
209
+ header << encrypted
210
+ end
211
+
212
+ if cipher_name
213
+ header << [cipher_name.length].pack('v')
214
+ header << cipher_name
215
+ end
216
+
217
+ if auth_tag
218
+ header << [auth_tag.length].pack('v')
219
+ header << auth_tag
220
+ end
221
+
222
+ header
223
+ end
224
+
225
+ private
226
+
227
+ FLAG_COMPRESSED = 0b1000_0000
228
+ FLAG_IV = 0b0100_0000
229
+ FLAG_KEY = 0b0010_0000
230
+ FLAG_CIPHER_NAME = 0b0001_0000
231
+ FLAG_AUTH_TAG = 0b0000_1000
232
+
233
+ attr_writer :auth_tag
234
+
235
+ # Extracts a string from the supplied buffer.
236
+ # The buffer starts with a 2 byte length indicator in little endian format.
237
+ #
238
+ # Parameters
239
+ # buffer [String]
240
+ # offset [Integer]
241
+ # Start position within the buffer.
242
+ #
243
+ # Returns [string, offset]
244
+ # string [String]
245
+ # The string copied from the buffer.
246
+ # offset [Integer]
247
+ # The new offset within the buffer.
248
+ def read_string(buffer, offset)
249
+ # TODO: Length check
250
+ # Exception when
251
+ # - offset exceeds length of buffer
252
+ # byteslice truncates when too long, but returns nil when start is beyond end of buffer
253
+ len = buffer.byteslice(offset, 2).unpack('v').first
254
+ offset += 2
255
+ out = buffer.byteslice(offset, len)
256
+ [out, offset + len]
257
+ end
258
+
259
+ end
260
+ end
@@ -0,0 +1,106 @@
1
+ # The key, iv and encrypted data are handled in their raw form, with no encoding.
2
+ module SymmetricEncryption
3
+ class Key
4
+ attr_reader :key, :iv, :cipher_name
5
+
6
+ # Returns [Key] from cipher data usually extracted from the configuration file.
7
+ #
8
+ # Supports N level deep key encrypting keys.
9
+ #
10
+ # Configuration keys:
11
+ # * key
12
+ # * encrypted_key
13
+ # * key_filename
14
+ def self.from_config(key: nil, key_filename: nil, encrypted_key: nil, key_env_var: nil,
15
+ iv:, key_encrypting_key: nil, cipher_name: 'aes-256-cbc')
16
+
17
+ if key_encrypting_key.is_a?(Hash)
18
+ # Recurse up the chain returning the parent key_encrypting_key
19
+ key_encrypting_key = from_config(cipher_name: cipher_name, **key_encrypting_key)
20
+ end
21
+
22
+ key ||=
23
+ if encrypted_key
24
+ raise(ArgumentError, "Missing mandatory :key_encrypting_key when config includes :encrypted_key") unless key_encrypting_key
25
+ Keystore::Memory.new(encrypted_key: encrypted_key, key_encrypting_key: key_encrypting_key).read
26
+ elsif key_filename
27
+ Keystore::File.new(file_name: key_filename, key_encrypting_key: key_encrypting_key).read
28
+ elsif key_env_var
29
+ raise(ArgumentError, "Missing mandatory :key_encrypting_key when config includes :key_env_var") unless key_encrypting_key
30
+ Keystore::Environment.new(key_env_var: key_env_var, key_encrypting_key: key_encrypting_key).read
31
+ end
32
+
33
+ new(key: key, iv: iv, cipher_name: cipher_name)
34
+ end
35
+
36
+ # Migrate a prior config.
37
+ #
38
+ # Note:
39
+ # * The config cannot be saved back to the config file once
40
+ # migrated, without generating new Key Encrypting Keys.
41
+ # * Only run this migration in the target environment so that the
42
+ # current key encrypting files are present.
43
+ def self.migrate_config!(config)
44
+ # Backward compatibility - Deprecated
45
+ private_rsa_key = config.delete(:private_rsa_key)
46
+
47
+ # Migrate old encrypted_iv
48
+ if (encrypted_iv = config.delete(:encrypted_iv)) && private_rsa_key
49
+ encrypted_iv = RSAKey.new(private_rsa_key).decrypt(encrypted_iv)
50
+ config[:iv] = ::Base64.decode64(encrypted_iv)
51
+ end
52
+
53
+ # Migrate old iv_filename
54
+ if (file_name = config.delete(:iv_filename)) && private_rsa_key
55
+ encrypted_iv = File.read(file_name)
56
+ config[:iv] = RSAKey.new(private_rsa_key).decrypt(encrypted_iv)
57
+ end
58
+
59
+ # Backward compatibility - Deprecated
60
+ config[:key_encrypting_key] = RSAKey.new(private_rsa_key) if private_rsa_key
61
+
62
+ # Migrate old encrypted_key to new binary format
63
+ if (encrypted_key = config[:encrypted_key]) && private_rsa_key
64
+ config[:encrypted_key] = ::Base64.decode64(encrypted_key)
65
+ end
66
+ end
67
+
68
+ def initialize(key: :random, iv: :random, cipher_name: 'aes-256-cbc')
69
+ @key = key == :random ? ::OpenSSL::Cipher.new(cipher_name).random_key : key
70
+ @iv = iv == :random ? ::OpenSSL::Cipher.new(cipher_name).random_iv : iv
71
+ @cipher_name = cipher_name
72
+ end
73
+
74
+ def encrypt(string)
75
+ return if string.nil?
76
+ string = string.to_s
77
+ return string if string.empty?
78
+
79
+ # Creates a new OpenSSL::Cipher with every call so that this key instance is thread-safe.
80
+ openssl_cipher = ::OpenSSL::Cipher.new(cipher_name)
81
+ openssl_cipher.encrypt
82
+ openssl_cipher.key = key
83
+ openssl_cipher.iv = iv
84
+
85
+ result = openssl_cipher.update(string)
86
+ result << openssl_cipher.final
87
+ end
88
+
89
+ def decrypt(encrypted_string)
90
+ return if encrypted_string.nil?
91
+ encrypted_string = encrypted_string.to_s
92
+ encrypted_string.force_encoding(SymmetricEncryption::BINARY_ENCODING)
93
+ return encrypted_string if encrypted_string.empty?
94
+
95
+ # Creates a new OpenSSL::Cipher with every call so that this key instance is thread-safe.
96
+ openssl_cipher = ::OpenSSL::Cipher.new(cipher_name)
97
+ openssl_cipher.decrypt
98
+ openssl_cipher.key = key
99
+ openssl_cipher.iv = iv
100
+
101
+ result = openssl_cipher.update(encrypted_string)
102
+ result << openssl_cipher.final
103
+ end
104
+
105
+ end
106
+ end
@@ -0,0 +1,90 @@
1
+ module SymmetricEncryption
2
+ module Keystore
3
+ # Store the encrypted encryption key in an environment variable
4
+ class Environment < Memory
5
+ attr_accessor :key_env_var, :encoding
6
+
7
+ # Returns [Hash] initial configuration for heroku.
8
+ # Displays the keys that need to be added to the heroku environment.
9
+ def self.new_config(app_name: 'symmetric-encryption',
10
+ environments: %i(development test release production),
11
+ cipher_name: 'aes-256-cbc')
12
+
13
+ configs = {}
14
+ environments.each do |environment|
15
+ environment = environment.to_sym
16
+ configs[environment] =
17
+ if %i(development test).include?(environment)
18
+ Keystore.dev_config
19
+ else
20
+ cfg = new_key_config(cipher_name: cipher_name, app_name: app_name, environment: environment)
21
+ {
22
+ ciphers: [cfg]
23
+ }
24
+ end
25
+ end
26
+ configs
27
+ end
28
+
29
+ # Returns [Hash] a new cipher, and writes its encrypted key file.
30
+ #
31
+ # Increments the supplied version number by 1.
32
+ def self.new_key_config(cipher_name:, app_name:, environment:, version: 0, dek: nil)
33
+ version >= 255 ? (version = 1) : (version += 1)
34
+
35
+ kek = SymmetricEncryption::Key.new(cipher_name: cipher_name)
36
+ dek ||= SymmetricEncryption::Key.new(cipher_name: cipher_name)
37
+
38
+ key_env_var = "#{app_name}_#{environment}_v#{version}".upcase.gsub('-', '_')
39
+ new(key_env_var: key_env_var, key_encrypting_key: kek).write(dek.key)
40
+
41
+ {
42
+ cipher_name: dek.cipher_name,
43
+ version: version,
44
+ key_env_var: key_env_var,
45
+ iv: dek.iv,
46
+ key_encrypting_key: {
47
+ key: kek.key,
48
+ iv: kek.iv,
49
+ }
50
+ }
51
+ end
52
+
53
+ # Stores the Encryption key in an environment var.
54
+ # Secures the Encryption key by encrypting it with a key encryption key.
55
+ def initialize(key_encrypting_key:, key_env_var:, encoding: :base64strict)
56
+ @key_env_var = key_env_var
57
+ @key_encrypting_key = key_encrypting_key
58
+ @encoding = encoding
59
+ end
60
+
61
+ # Returns the Encryption key in the clear.
62
+ def read
63
+ encrypted = ENV[key_env_var]
64
+ raise "The Environment Variable #{key_env_var} must be set with the encrypted encryption key." unless encrypted
65
+ binary = encoder.decode(encrypted)
66
+ key_encrypting_key.decrypt(binary)
67
+ end
68
+
69
+ # Write the encrypted Encryption key to `encrypted_key` attribute.
70
+ def write(key)
71
+ encrypted_key = key_encrypting_key.encrypt(key)
72
+ puts "\n\n********************************************************************************"
73
+ puts "Add the environment key to Heroku:\n\n"
74
+ puts " heroku config:add #{key_env_var}=#{encoder.encode(encrypted_key)}"
75
+ puts
76
+ puts "Or, if using environment variables on another system set the environment variable as follows:\n\n"
77
+ puts " export #{key_env_var}=\"#{encoder.encode(encrypted_key)}\"\n\n"
78
+ puts "********************************************************************************"
79
+ end
80
+
81
+ private
82
+
83
+ # Returns [SymmetricEncryption::Encoder] the encoder to use for the current encoding.
84
+ def encoder
85
+ @encoder ||= SymmetricEncryption::Encoder[encoding]
86
+ end
87
+
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,102 @@
1
+ module SymmetricEncryption
2
+ module Keystore
3
+ class File
4
+ attr_accessor :file_name, :key_encrypting_key
5
+
6
+ # Returns [Hash] initial configuration.
7
+ # Generates the encrypted key file for every environment except development and test.
8
+ def self.new_config(key_path: '/etc/symmetric-encryption',
9
+ app_name: 'symmetric-encryption',
10
+ environments: %i(development test release production),
11
+ cipher_name: 'aes-256-cbc')
12
+
13
+ configs = {}
14
+ environments.each do |environment|
15
+ environment = environment.to_sym
16
+ configs[environment] =
17
+ if %i(development test).include?(environment)
18
+ Keystore.dev_config
19
+ else
20
+ cfg = new_key_config(key_path: key_path, cipher_name: cipher_name, app_name: app_name, environment: environment)
21
+ {
22
+ ciphers: [cfg]
23
+ }
24
+ end
25
+ end
26
+ configs
27
+ end
28
+
29
+ # Returns [Hash] a new cipher, and writes its encrypted key file.
30
+ #
31
+ # Increments the supplied version number by 1.
32
+ def self.new_key_config(key_path:, cipher_name:, app_name:, environment:, version: 0, dek: nil)
33
+ version >= 255 ? (version = 1) : (version += 1)
34
+
35
+ dek ||= SymmetricEncryption::Key.new(cipher_name: cipher_name)
36
+ kek = SymmetricEncryption::Key.new(cipher_name: cipher_name)
37
+ kekek = SymmetricEncryption::Key.new(cipher_name: cipher_name)
38
+
39
+ dek_file_name = ::File.join(key_path, "#{app_name}_#{environment}_v#{version}.encrypted_key")
40
+ new(file_name: dek_file_name, key_encrypting_key: kek).write(dek.key)
41
+
42
+ kekek_file_name = ::File.join(key_path, "#{app_name}_#{environment}_v#{version}.kekek")
43
+ new(file_name: kekek_file_name).write(kekek.key)
44
+
45
+ {
46
+ cipher_name: dek.cipher_name,
47
+ version: version,
48
+ key_filename: dek_file_name,
49
+ iv: dek.iv,
50
+ key_encrypting_key: {
51
+ encrypted_key: kekek.encrypt(kek.key),
52
+ iv: kek.iv,
53
+ key_encrypting_key: {
54
+ key_filename: kekek_file_name,
55
+ iv: kekek.iv
56
+ }
57
+ }
58
+ }
59
+ end
60
+
61
+ # Stores the Encryption key in a file.
62
+ # Secures the Encryption key by encrypting it with a key encryption key.
63
+ def initialize(file_name:, key_encrypting_key: nil)
64
+ @file_name = file_name
65
+ @key_encrypting_key = key_encrypting_key
66
+ end
67
+
68
+ # Returns the Encryption key in the clear.
69
+ def read
70
+ # TODO: Validate that file is not globally readable.
71
+ raise(SymmetricEncryption::ConfigError, "Symmetric Encryption key file: '#{file_name}' not found") unless ::File.exist?(file_name)
72
+
73
+ data = read_from_file
74
+ key_encrypting_key ? key_encrypting_key.decrypt(data) : data
75
+ end
76
+
77
+ # Encrypt and write the key to file.
78
+ def write(key)
79
+ data = key_encrypting_key ? key_encrypting_key.encrypt(key) : key
80
+ write_to_file(data)
81
+ end
82
+
83
+ private
84
+
85
+ # Read from the file, raising an exception if it is not found
86
+ def read_from_file
87
+ ::File.open(file_name, 'rb') { |f| f.read }
88
+ rescue Errno::ENOENT
89
+ raise(SymmetricEncryption::ConfigError, "Symmetric Encryption key file: '#{file_name}' not found or readable")
90
+ end
91
+
92
+ # Write to the supplied file_name, backing up the existing file if present
93
+ def write_to_file(data)
94
+ key_path = ::File.dirname(file_name)
95
+ ::FileUtils.mkdir_p(key_path) unless ::File.directory?(key_path)
96
+ ::File.rename(file_name, "#{file_name}.#{Time.now.to_i}") if ::File.exist?(file_name)
97
+ ::File.open(file_name, 'wb') { |file| file.write(data) }
98
+ end
99
+
100
+ end
101
+ end
102
+ end
@@ -0,0 +1,53 @@
1
+ module SymmetricEncryption
2
+ module Keystore
3
+ class Memory
4
+ attr_accessor :key_encrypting_key
5
+ attr_reader :encrypted_key
6
+
7
+ # Returns [Hash] a new cipher, and writes its encrypted key file.
8
+ #
9
+ # Increments the supplied version number by 1.
10
+ #
11
+ # Notes:
12
+ # * For development and testing purposes only!!
13
+ # * Never store the encrypted encryption key in the source code / config file.
14
+ def self.new_key_config(cipher_name:, app_name:, environment:, version: 0, dek: nil)
15
+ version >= 255 ? (version = 1) : (version += 1)
16
+
17
+ kek = SymmetricEncryption::Key.new(cipher_name: cipher_name)
18
+ dek ||= SymmetricEncryption::Key.new(cipher_name: cipher_name)
19
+
20
+ encrypted_key = new(key_encrypting_key: kek).write(dek.key)
21
+
22
+ {
23
+ cipher_name: cipher_name,
24
+ version: version,
25
+ encrypted_key: encrypted_key,
26
+ iv: iv,
27
+ key_encrypting_key: {
28
+ key: kek.key,
29
+ iv: kek.iv,
30
+ }
31
+ }
32
+ end
33
+
34
+ # Stores the Encryption key in a string.
35
+ # Secures the Encryption key by encrypting it with a key encryption key.
36
+ def initialize(encrypted_key: nil, key_encrypting_key:)
37
+ @encrypted_key = encrypted_key
38
+ @key_encrypting_key = key_encrypting_key
39
+ end
40
+
41
+ # Returns the Encryption key in the clear.
42
+ def read
43
+ key_encrypting_key.decrypt(encrypted_key)
44
+ end
45
+
46
+ # Write the encrypted Encryption key to `encrypted_key` attribute.
47
+ def write(key)
48
+ self.encrypted_key = key_encrypting_key.encrypt(key)
49
+ end
50
+
51
+ end
52
+ end
53
+ end