symmetric-encryption 3.7.2 → 3.8.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.
@@ -0,0 +1,75 @@
1
+ module SymmetricEncryption
2
+ # For coercing data types to from strings
3
+ module Coerce
4
+ TYPE_MAP = {
5
+ string: String,
6
+ integer: Integer,
7
+ float: Float,
8
+ decimal: BigDecimal,
9
+ datetime: DateTime,
10
+ time: Time,
11
+ date: Date
12
+ }
13
+
14
+ # Coerce given value into given type
15
+ # Does not coerce json or yaml values
16
+ def self.coerce(value, type, from_type=nil)
17
+ return if value.nil? || (value.is_a?(String) && (value !~ /[^[:space:]]/))
18
+
19
+ from_type ||= value.class
20
+ case type
21
+ when :json
22
+ value
23
+ when :yaml
24
+ value
25
+ else
26
+ coercer = Coercible::Coercer.new
27
+ coercer[from_type].send("to_#{type}".to_sym, value)
28
+ end
29
+ end
30
+
31
+ # Uses coercible gem to coerce values from strings into the target type
32
+ # Note: if the type is :string, then the value is returned as is, and the
33
+ # coercible gem is not used at all.
34
+ def self.coerce_from_string(value, type)
35
+ return if value.nil?
36
+ case type
37
+ when :string
38
+ value
39
+ when :json
40
+ JSON.load(value)
41
+ when :yaml
42
+ YAML.load(value)
43
+ else
44
+ self.coerce(value, type, String)
45
+ end
46
+ end
47
+
48
+ # Uses coercible gem to coerce values to strings from the specified type
49
+ # Note: if the type is :string, and value is not nil, then #to_s is called
50
+ # on the value and the coercible gem is not used at all.
51
+ def self.coerce_to_string(value, type)
52
+ return if value.nil?
53
+
54
+ case type
55
+ when :string
56
+ value.to_s
57
+ when :json
58
+ value.to_json
59
+ when :yaml
60
+ value.to_yaml
61
+ else
62
+ self.coerce(value, :string, coercion_type(type, value))
63
+ end
64
+ end
65
+
66
+ # Returns the correct coercion type to use for the specified symbol and value
67
+ def self.coercion_type(symbol, value)
68
+ if symbol == :boolean
69
+ value.class
70
+ else
71
+ TYPE_MAP[symbol]
72
+ end
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,88 @@
1
+ module SymmetricEncryption
2
+ module Config
3
+ # Load the Encryption Configuration from a YAML file
4
+ # filename:
5
+ # Name of file to read.
6
+ # Mandatory for non-Rails apps
7
+ # Default: Rails.root/config/symmetric-encryption.yml
8
+ # environment:
9
+ # Which environments config to load. Usually: production, development, etc.
10
+ # Default: Rails.env
11
+ def self.load!(filename=nil, environment=nil)
12
+ config = read_config(filename, environment)
13
+ ciphers = extract_ciphers(config)
14
+
15
+ SymmetricEncryption.cipher = ciphers.shift
16
+ SymmetricEncryption.secondary_ciphers = ciphers
17
+ true
18
+ end
19
+
20
+ private
21
+
22
+ # Returns [Hash] the configuration for the supplied environment
23
+ def self.read_config(filename=nil, environment=nil)
24
+ config_filename = filename || File.join(Rails.root, 'config', 'symmetric-encryption.yml')
25
+ cfg = YAML.load(ERB.new(File.new(config_filename).read).result)[environment || Rails.env]
26
+ extract_config(cfg)
27
+ end
28
+
29
+ # Returns [ private_rsa_key, ciphers ] config
30
+ def self.extract_config(config)
31
+ config = deep_symbolize_keys(config)
32
+
33
+ # Old format?
34
+ unless config.has_key?(:ciphers)
35
+ config = {
36
+ private_rsa_key: config.delete(:private_rsa_key),
37
+ ciphers: [config]
38
+ }
39
+ end
40
+
41
+ # Old format cipher name?
42
+ config[:ciphers] = config[:ciphers].collect do |cipher|
43
+ if old_key_name_cipher = cipher.delete(:cipher)
44
+ cipher[:cipher_name] = old_key_name_cipher
45
+ end
46
+ cipher
47
+ end
48
+ config
49
+ end
50
+
51
+ # Returns [Array(SymmetricEncrytion::Cipher)] ciphers specified in the configuration file
52
+ #
53
+ # Read the configuration from the YAML file and return in the latest format
54
+ #
55
+ # filename:
56
+ # Name of file to read.
57
+ # Mandatory for non-Rails apps
58
+ # Default: Rails.root/config/symmetric-encryption.yml
59
+ # environment:
60
+ # Which environments config to load. Usually: production, development, etc.
61
+ def self.extract_ciphers(config)
62
+ # RSA key to decrypt key files
63
+ private_rsa_key = config[:private_rsa_key]
64
+
65
+ config[:ciphers].collect do |cipher_config|
66
+ Cipher.new({private_rsa_key: private_rsa_key}.merge(cipher_config))
67
+ end
68
+ end
69
+
70
+ # Iterate through the Hash symbolizing all keys
71
+ def self.deep_symbolize_keys(x)
72
+ case x
73
+ when Hash
74
+ result = {}
75
+ x.each_pair do |key, value|
76
+ key = key.to_sym if key.is_a?(String)
77
+ result[key] = deep_symbolize_keys(value)
78
+ end
79
+ result
80
+ when Array
81
+ x.collect { |i| deep_symbolize_keys(i) }
82
+ else
83
+ x
84
+ end
85
+ end
86
+
87
+ end
88
+ end
@@ -41,7 +41,7 @@ module ActiveRecord #:nodoc:
41
41
  # Ignore failures since the table may not yet actually exist
42
42
  define_attribute_methods rescue nil
43
43
 
44
- options = params.last.is_a?(Hash) ? params.pop.dup : {}
44
+ options = params.last.is_a?(Hash) ? params.pop.dup : {}
45
45
 
46
46
  params.each do |attribute|
47
47
  SymmetricEncryption::Generator.generate_decrypted_accessors(self, attribute, "encrypted_#{attribute}", options)
@@ -129,7 +129,7 @@ module ActiveRecord #:nodoc:
129
129
  attribute_names.each_with_index do |attribute, index|
130
130
  encrypted_name = "encrypted_#{attribute}"
131
131
  if method_defined? encrypted_name.to_sym
132
- args[index] = ::SymmetricEncryption.encrypt(args[index])
132
+ args[index] = ::SymmetricEncryption.encrypt(args[index])
133
133
  attribute_names[index] = encrypted_name
134
134
  end
135
135
  end
@@ -10,7 +10,7 @@
10
10
  # Mongoid.load!('config/mongoid.yml')
11
11
  #
12
12
  # # Initialize SymmetricEncryption in a standalone environment. In a Rails app this is not required
13
- # SymmetricEncryption.load!('config/symmetric-encryption.yml', 'test')
13
+ # SymmetricEncryption::Config.load!('config/symmetric-encryption.yml', 'test')
14
14
  #
15
15
  # class Person
16
16
  # include Mongoid::Document
@@ -89,7 +89,7 @@
89
89
  # @return [ Field ] The generated field
90
90
  Mongoid::Fields.option :encrypted do |model, field, options|
91
91
  if options != false
92
- options = options.is_a?(Hash) ? options.dup : {}
92
+ options = options.is_a?(Hash) ? options.dup : {}
93
93
  encrypted_field_name = field.name
94
94
 
95
95
  # Support overriding the name of the decrypted attribute
@@ -24,7 +24,7 @@ module SymmetricEncryption
24
24
  # Also updates the encrypted field with the encrypted value
25
25
  # Freeze the decrypted field value so that it is not modified directly
26
26
  def #{decrypted_name}=(value)
27
- v = SymmetricEncryption::coerce(value, :#{type})
27
+ v = SymmetricEncryption::Coerce.coerce(value, :#{type})
28
28
  self.#{encrypted_name} = @stored_#{encrypted_name} = ::SymmetricEncryption.encrypt(v,#{random_iv},#{compress},:#{type})
29
29
  @#{decrypted_name} = v.freeze
30
30
  end
@@ -40,6 +40,10 @@ module SymmetricEncryption
40
40
  @#{decrypted_name}
41
41
  end
42
42
 
43
+ # Map changes to encrypted value to unencrypted equivalent
44
+ def #{decrypted_name}_changed?
45
+ #{encrypted_name}_changed?
46
+ end
43
47
  EOS
44
48
  end
45
49
  end
@@ -17,7 +17,7 @@ module SymmetricEncryption #:nodoc:
17
17
  config.symmetric_encryption = ::SymmetricEncryption
18
18
 
19
19
  rake_tasks do
20
- load "symmetric_encryption/railties/symmetric_encryption.rake"
20
+ load 'symmetric_encryption/railties/symmetric_encryption.rake'
21
21
  end
22
22
 
23
23
  # Initialize Symmetry. This will look for a symmetry.yml in the config
@@ -35,9 +35,9 @@ module SymmetricEncryption #:nodoc:
35
35
  config.before_configuration do
36
36
  # Check if already configured
37
37
  unless ::SymmetricEncryption.cipher?
38
- config_file = Rails.root.join("config", "symmetric-encryption.yml")
38
+ config_file = Rails.root.join('config', 'symmetric-encryption.yml')
39
39
  if config_file.file?
40
- ::SymmetricEncryption.load!(config_file, Rails.env)
40
+ ::SymmetricEncryption::Config.load!(config_file, Rails.env)
41
41
  else
42
42
  puts "\nSymmetric Encryption config not found."
43
43
  puts "To generate one for the first time: rails generate symmetric_encryption:config\n\n"
@@ -17,11 +17,11 @@ namespace :symmetric_encryption do
17
17
  password2 = 0
18
18
 
19
19
  while password1 != password2
20
- password1 = HighLine.new.ask("Enter the value to encrypt:") { |q| q.echo = "*" }
21
- password2 = HighLine.new.ask("Re-enter the value to encrypt:") { |q| q.echo = "*" }
20
+ password1 = HighLine.new.ask('Enter the value to encrypt:') { |q| q.echo = '*' }
21
+ password2 = HighLine.new.ask('Re-enter the value to encrypt:') { |q| q.echo = '*' }
22
22
 
23
23
  if (password1 != password2)
24
- puts "Passwords do not match, please try again"
24
+ puts 'Passwords do not match, please try again'
25
25
  end
26
26
  end
27
27
  puts "\nEncrypted: #{SymmetricEncryption.encrypt(password1)}\n\n"
@@ -51,7 +51,7 @@ namespace :symmetric_encryption do
51
51
  end
52
52
  puts "\n#{output_filename} now contains the decrypted contents of #{input_filename}\n\n"
53
53
  else
54
- puts "Missing input and/or output filename. Usage:"
54
+ puts 'Missing input and/or output filename. Usage:'
55
55
  puts ' INFILE="encrypted_filename" OUTFILE="filename" rake symmetric_encryption:decrypt_file'
56
56
  end
57
57
  end
@@ -74,9 +74,9 @@ namespace :symmetric_encryption do
74
74
  end
75
75
  puts "\n#{output_filename} now contains the encrypted #{"and compressed " if compress}contents of #{input_filename}\n\n"
76
76
  else
77
- puts "Missing input and/or output filename. Usage:"
77
+ puts 'Missing input and/or output filename. Usage:'
78
78
  puts ' INFILE="filename" OUTFILE="encrypted_filename" rake symmetric_encryption:encrypt_file'
79
- puts "To compress the file before encrypting:"
79
+ puts 'To compress the file before encrypting:'
80
80
  puts ' COMPRESS=1 INFILE="filename" OUTFILE="encrypted_filename" rake symmetric_encryption:encrypt_file'
81
81
  end
82
82
  end
@@ -13,6 +13,6 @@
13
13
  # # => true
14
14
  class SymmetricEncryptionValidator < ActiveModel::EachValidator
15
15
  def validate_each(record, attribute, value)
16
- record.errors.add(attribute, "must be a value encrypted using SymmetricEncryption.encrypt") unless SymmetricEncryption.encrypted?(value)
16
+ record.errors.add(attribute, 'must be a value encrypted using SymmetricEncryption.encrypt') unless SymmetricEncryption.encrypted?(value)
17
17
  end
18
18
  end
@@ -94,12 +94,12 @@ module SymmetricEncryption
94
94
  # Returns [true|false] whether the file or stream contains any data
95
95
  # excluding the header should it have one
96
96
  def self.empty?(filename_or_stream)
97
- open(filename_or_stream) {|file| file.eof? }
97
+ open(filename_or_stream) { |file| file.eof? }
98
98
  end
99
99
 
100
100
  # Returns [true|false] whether the file contains the encryption header
101
101
  def self.header_present?(filename)
102
- ::File.open(filename, 'rb') {|file| new(file).header_present?}
102
+ ::File.open(filename, 'rb') { |file| new(file).header_present? }
103
103
  end
104
104
 
105
105
  # After opening a file Returns [true|false] whether the file being
@@ -109,7 +109,7 @@ module SymmetricEncryption
109
109
  end
110
110
 
111
111
  # Decrypt data before reading from the supplied stream
112
- def initialize(ios,options={})
112
+ def initialize(ios, options={})
113
113
  @ios = ios
114
114
  @buffer_size = options.fetch(:buffer_size, 4096).to_i
115
115
  @version = options[:version]
@@ -193,12 +193,12 @@ module SymmetricEncryption
193
193
  elsif @read_buffer.length > length
194
194
  data = @read_buffer.slice!(0..length-1)
195
195
  else
196
- data = @read_buffer
196
+ data = @read_buffer
197
197
  @read_buffer = ''
198
198
  end
199
199
  else
200
200
  # Capture anything already in the buffer
201
- data = @read_buffer
201
+ data = @read_buffer
202
202
  @read_buffer = ''
203
203
 
204
204
  if !@ios.eof?
@@ -216,14 +216,14 @@ module SymmetricEncryption
216
216
  # Raises EOFError on eof
217
217
  # The stream must be opened for reading or an IOError will be raised.
218
218
  def readline(sep_string = "\n")
219
- gets(sep_string) || raise(EOFError.new("End of file reached when trying to read a line"))
219
+ gets(sep_string) || raise(EOFError.new('End of file reached when trying to read a line'))
220
220
  end
221
221
 
222
222
  # Reads a single decrypted line from the file up to and including the optional sep_string.
223
223
  # A sep_string of nil reads the entire contents of the file
224
224
  # Returns nil on eof
225
225
  # The stream must be opened for reading or an IOError will be raised.
226
- def gets(sep_string,length=nil)
226
+ def gets(sep_string, length=nil)
227
227
  return read(length) if sep_string.nil?
228
228
 
229
229
  # Read more data until we get the sep_string
@@ -232,8 +232,8 @@ module SymmetricEncryption
232
232
  read_block
233
233
  end
234
234
  index ||= -1
235
- data = @read_buffer.slice!(0..index)
236
- @pos += data.length
235
+ data = @read_buffer.slice!(0..index)
236
+ @pos += data.length
237
237
  return nil if data.length == 0 && eof?
238
238
  data
239
239
  end
@@ -300,7 +300,7 @@ module SymmetricEncryption
300
300
  size = 0
301
301
  while !eof
302
302
  read_block
303
- size += @read_buffer.size
303
+ size += @read_buffer.size
304
304
  @read_buffer = ''
305
305
  end
306
306
  rewind
@@ -316,13 +316,15 @@ module SymmetricEncryption
316
316
 
317
317
  # Read the header from the file if present
318
318
  def read_header
319
- @pos = 0
319
+ @pos = 0
320
320
 
321
321
  # Read first block and check for the header
322
- buf = @ios.read(@buffer_size)
322
+ buf = @ios.read(@buffer_size)
323
323
 
324
324
  # Use cipher specified in header, or global cipher if it has no header
325
- iv, key, cipher_name, decryption_cipher = nil
325
+ iv, key = nil
326
+ cipher_name = nil
327
+ decryption_cipher = nil
326
328
  if header = SymmetricEncryption::Cipher.parse_header!(buf)
327
329
  @header_present = true
328
330
  @compressed = header.compressed
@@ -340,7 +342,7 @@ module SymmetricEncryption
340
342
  @stream_cipher = ::OpenSSL::Cipher.new(cipher_name)
341
343
  @stream_cipher.decrypt
342
344
  @stream_cipher.key = key || decryption_cipher.send(:key)
343
- @stream_cipher.iv = iv || decryption_cipher.iv
345
+ @stream_cipher.iv = iv || decryption_cipher.iv
344
346
 
345
347
  # First call to #update should return an empty string anyway
346
348
  if buf && buf.length > 0
@@ -10,9 +10,9 @@ require 'erb'
10
10
  module SymmetricEncryption
11
11
 
12
12
  # Defaults
13
- @@cipher = nil
13
+ @@cipher = nil
14
14
  @@secondary_ciphers = []
15
- @@select_cipher = nil
15
+ @@select_cipher = nil
16
16
 
17
17
  # List of types supported when encrypting or decrypting data
18
18
  #
@@ -26,7 +26,7 @@ module SymmetricEncryption
26
26
  # :date => Date
27
27
  # :json => Uses JSON serialization, useful for hashes and arrays
28
28
  # :yaml => Uses YAML serialization, useful for hashes and arrays
29
- COERCION_TYPES = [:string, :integer, :float, :decimal, :datetime, :time, :date, :boolean, :json, :yaml]
29
+ COERCION_TYPES = [:string, :integer, :float, :decimal, :datetime, :time, :date, :boolean, :json, :yaml]
30
30
 
31
31
  # Set the Primary Symmetric Cipher to be used
32
32
  #
@@ -49,7 +49,7 @@ module SymmetricEncryption
49
49
  def self.cipher(version = nil)
50
50
  raise(SymmetricEncryption::ConfigError, 'Call SymmetricEncryption.load! or SymmetricEncryption.cipher= prior to encrypting or decrypting data') unless @@cipher
51
51
  return @@cipher if version.nil? || (@@cipher.version == version)
52
- secondary_ciphers.find {|c| c.version == version} || (@@cipher if version == 0)
52
+ secondary_ciphers.find { |c| c.version == version } || (@@cipher if version == 0)
53
53
  end
54
54
 
55
55
  # Returns whether a primary cipher has been set
@@ -59,9 +59,9 @@ module SymmetricEncryption
59
59
 
60
60
  # Set the Secondary Symmetric Ciphers Array to be used
61
61
  def self.secondary_ciphers=(secondary_ciphers)
62
- raise(ArgumentError, "secondary_ciphers must be a collection") unless secondary_ciphers.respond_to? :each
62
+ raise(ArgumentError, 'secondary_ciphers must be a collection') unless secondary_ciphers.respond_to? :each
63
63
  secondary_ciphers.each do |cipher|
64
- raise(ArgumentError, "secondary_ciphers can only consist of SymmetricEncryption::Ciphers") unless cipher.respond_to?(:encrypt) && cipher.respond_to?(:decrypt)
64
+ raise(ArgumentError, 'secondary_ciphers can only consist of SymmetricEncryption::Ciphers') unless cipher.respond_to?(:encrypt) && cipher.respond_to?(:decrypt)
65
65
  end
66
66
  @@secondary_ciphers = secondary_ciphers
67
67
  end
@@ -109,28 +109,27 @@ module SymmetricEncryption
109
109
  raise(SymmetricEncryption::ConfigError, 'Call SymmetricEncryption.load! or SymmetricEncryption.cipher= prior to encrypting or decrypting data') unless @@cipher
110
110
  return encrypted_and_encoded_string if encrypted_and_encoded_string.nil? || (encrypted_and_encoded_string == '')
111
111
 
112
- str = encrypted_and_encoded_string.to_s
112
+ str = encrypted_and_encoded_string.to_s
113
113
 
114
114
  # Decode before decrypting supplied string
115
115
  decoded = @@cipher.decode(str)
116
116
  return unless decoded
117
117
  return decoded if decoded.empty?
118
118
 
119
- decrypted = if header = Cipher.parse_header!(decoded)
120
- header.decryption_cipher.binary_decrypt(decoded, header)
121
- else
122
- # Use cipher_selector if present to decide which cipher to use
123
- c = @@select_cipher.nil? ? cipher(version) : @@select_cipher.call(str, decoded)
124
- c.binary_decrypt(decoded)
125
- end
126
-
127
- if defined?(Encoding)
128
- # Try to force result to UTF-8 encoding, but if it is not valid, force it back to Binary
129
- unless decrypted.force_encoding(SymmetricEncryption::UTF8_ENCODING).valid_encoding?
130
- decrypted.force_encoding(SymmetricEncryption::BINARY_ENCODING)
119
+ decrypted =
120
+ if header = Cipher.parse_header!(decoded)
121
+ header.decryption_cipher.binary_decrypt(decoded, header)
122
+ else
123
+ # Use cipher_selector if present to decide which cipher to use
124
+ c = @@select_cipher.nil? ? cipher(version) : @@select_cipher.call(str, decoded)
125
+ c.binary_decrypt(decoded)
131
126
  end
127
+
128
+ # Try to force result to UTF-8 encoding, but if it is not valid, force it back to Binary
129
+ unless decrypted.force_encoding(SymmetricEncryption::UTF8_ENCODING).valid_encoding?
130
+ decrypted.force_encoding(SymmetricEncryption::BINARY_ENCODING)
132
131
  end
133
- coerce_from_string(decrypted, type)
132
+ Coerce.coerce_from_string(decrypted, type)
134
133
  end
135
134
 
136
135
  # AES Symmetric Encryption of supplied string
@@ -179,7 +178,7 @@ module SymmetricEncryption
179
178
  raise(SymmetricEncryption::ConfigError, 'Call SymmetricEncryption.load! or SymmetricEncryption.cipher= prior to encrypting or decrypting data') unless @@cipher
180
179
 
181
180
  # Encrypt and then encode the supplied string
182
- @@cipher.encrypt(coerce_to_string(str, type), random_iv, compress)
181
+ @@cipher.encrypt(Coerce.coerce_to_string(str, type), random_iv, compress)
183
182
  end
184
183
 
185
184
  # Invokes decrypt
@@ -255,10 +254,7 @@ module SymmetricEncryption
255
254
  # Which environments config to load. Usually: production, development, etc.
256
255
  # Default: Rails.env
257
256
  def self.load!(filename=nil, environment=nil)
258
- ciphers = read_config(filename, environment)
259
- @@cipher = ciphers.shift
260
- @@secondary_ciphers = ciphers
261
- true
257
+ Config.load!(filename, environment)
262
258
  end
263
259
 
264
260
  # Generate new random symmetric keys for use with this Encryption library
@@ -266,53 +262,16 @@ module SymmetricEncryption
266
262
  # Note: Only the current Encryption key settings are used
267
263
  #
268
264
  # Creates Symmetric Key .key
269
- # and initilization vector .iv
265
+ # and initialization vector .iv
270
266
  # which is encrypted with the above Public key
271
267
  #
272
268
  # Existing key files will be renamed if present
273
269
  def self.generate_symmetric_key_files(filename=nil, environment=nil)
274
- config_filename = filename || File.join(Rails.root, "config", "symmetric-encryption.yml")
275
- config = YAML.load(ERB.new(File.new(config_filename).read).result)[environment || Rails.env]
276
-
277
- # RSA key to decrypt key files
278
- private_rsa_key = config.delete('private_rsa_key')
279
- raise(SymmetricEncryption::ConfigError, "The configuration file must contain a 'private_rsa_key' parameter to generate symmetric keys") unless private_rsa_key
280
- rsa_key = OpenSSL::PKey::RSA.new(private_rsa_key)
281
-
282
- # Check if config file contains 1 or multiple ciphers
283
- ciphers = config.delete('ciphers')
284
- cfg = ciphers.nil? ? config : ciphers.first
285
-
286
- # Convert keys to symbols
287
- cipher_cfg = {}
288
- cfg.each_pair{|k,v| cipher_cfg[k.to_sym] = v}
289
-
290
- cipher_name = cipher_cfg[:cipher_name] || cipher_cfg[:cipher]
291
-
292
- # Generate a new Symmetric Key pair
293
- iv_filename = cipher_cfg[:iv_filename]
294
- key_pair = SymmetricEncryption::Cipher.random_key_pair(cipher_name || 'aes-256-cbc')
295
-
296
- if key_filename = cipher_cfg[:key_filename]
297
- # Save symmetric key after encrypting it with the private RSA key, backing up existing files if present
298
- File.rename(key_filename, "#{key_filename}.#{Time.now.to_i}") if File.exist?(key_filename)
299
- File.open(key_filename, 'wb') {|file| file.write( rsa_key.public_encrypt(key_pair[:key]) ) }
300
- puts("Generated new Symmetric Key for encryption. Please copy #{key_filename} to the other web servers in #{environment}.")
301
- elsif !cipher_cfg[:key]
302
- key = rsa_key.public_encrypt(key_pair[:key])
303
- puts "Generated new Symmetric Key for encryption. Set the KEY environment variable in #{environment} to:"
304
- puts ::Base64.encode64(key)
305
- end
270
+ config = Config.read_config(filename, environment)
306
271
 
307
- if iv_filename
308
- File.rename(iv_filename, "#{iv_filename}.#{Time.now.to_i}") if File.exist?(iv_filename)
309
- File.open(iv_filename, 'wb') {|file| file.write( rsa_key.public_encrypt(key_pair[:iv]) ) }
310
- puts("Generated new Symmetric Key for encryption. Please copy #{iv_filename} to the other web servers in #{environment}.")
311
- elsif !cipher_cfg[:iv]
312
- iv = rsa_key.public_encrypt(key_pair[:iv])
313
- puts "Generated new Symmetric Key for encryption. Set the IV environment variable in #{environment} to:"
314
- puts ::Base64.encode64(iv)
315
- end
272
+ # Only regenerating the first configured cipher
273
+ cipher_config = config[:ciphers].first
274
+ Cipher.generate_random_keys({environment: environment, private_rsa_key: config[:private_rsa_key]}.merge(cipher_config))
316
275
  end
317
276
 
318
277
  # Generate a 22 character random password
@@ -323,225 +282,11 @@ module SymmetricEncryption
323
282
  # Binary encrypted data includes this magic header so that we can quickly
324
283
  # identify binary data versus base64 encoded data that does not have this header
325
284
  unless defined? MAGIC_HEADER
326
- MAGIC_HEADER = '@EnC'
327
- MAGIC_HEADER_SIZE = MAGIC_HEADER.size
285
+ MAGIC_HEADER = '@EnC'
286
+ MAGIC_HEADER_SIZE = MAGIC_HEADER.size
328
287
  MAGIC_HEADER_UNPACK = "a#{MAGIC_HEADER_SIZE}v"
329
288
  end
330
289
 
331
- protected
332
-
333
- # Returns [Array(SymmetricEncrytion::Cipher)] ciphers specified in the configuration file
334
- #
335
- # Read the configuration from the YAML file and return in the latest format
336
- #
337
- # filename:
338
- # Name of file to read.
339
- # Mandatory for non-Rails apps
340
- # Default: Rails.root/config/symmetric-encryption.yml
341
- # environment:
342
- # Which environments config to load. Usually: production, development, etc.
343
- def self.read_config(filename=nil, environment=nil)
344
- config_filename = filename || File.join(Rails.root, "config", "symmetric-encryption.yml")
345
- config = YAML.load(ERB.new(File.new(config_filename).read).result)[environment || Rails.env]
346
-
347
- # RSA key to decrypt key files
348
- private_rsa_key = config.delete('private_rsa_key')
349
-
350
- if ciphers = config.delete('ciphers')
351
- ciphers.collect {|cipher_conf| cipher_from_config(cipher_conf, private_rsa_key)}
352
- else
353
- [cipher_from_config(config, private_rsa_key)]
354
- end
355
- end
356
-
357
- # Returns an instance of SymmetricEncryption::Cipher created from
358
- # the supplied configuration and optional rsa_encryption_key
359
- #
360
- # Raises an Exception on failure
361
- #
362
- # Parameters:
363
- # cipher_conf Hash:
364
- # :cipher_name
365
- # Encryption cipher name for the symmetric encryption key
366
- #
367
- # :version
368
- # The version number of this cipher
369
- # Default: 0
370
- #
371
- # :encoding [Symbol]
372
- # Encoding to use after encrypting with this cipher
373
- #
374
- # :always_add_header
375
- # Whether to always include the header when encrypting data.
376
- # Highly recommended to set this value to true.
377
- # Increases the length of the encrypted data by 6 bytes, but makes
378
- # migration to a new key trivial
379
- # Default: false
380
- #
381
- # :key
382
- # The actual key to use for encryption/decryption purposes
383
- #
384
- # :key_filename
385
- # Name of file containing symmetric key encrypted using the public
386
- # key from the private_rsa_key
387
- #
388
- # :encrypted_key
389
- # Symmetric key encrypted using the public key from the private_rsa_key
390
- # and then Base64 encoded
391
- #
392
- # :iv
393
- # Optional: The actual iv to use for encryption/decryption purposes
394
- #
395
- # :encrypted_iv
396
- # Initialization vector encrypted using the public key from the private_rsa_key
397
- # and then Base64 encoded
398
- #
399
- # :iv_filename
400
- # Optional: Name of file containing symmetric key initialization vector
401
- # encrypted using the public key from the private_rsa_key
402
- #
403
- # private_rsa_key [String]
404
- # RSA Key used to decrypt key and iv as applicable
405
- def self.cipher_from_config(cipher_conf, private_rsa_key=nil)
406
- config = {}
407
- cipher_conf.each_pair{|k,v| config[k.to_sym] = v}
408
-
409
- # To decrypt encrypted key or iv files
410
- rsa = OpenSSL::PKey::RSA.new(private_rsa_key) if private_rsa_key
411
-
412
- # Load Encrypted Symmetric keys
413
- if key_filename = config.delete(:key_filename)
414
- raise(SymmetricEncryption::ConfigError, "Missing mandatory config parameter :private_rsa_key when :key_filename is supplied") unless rsa
415
- encrypted_key = begin
416
- File.open(key_filename, 'rb'){|f| f.read}
417
- rescue Errno::ENOENT
418
- puts "\nSymmetric Encryption key file: '#{key_filename}' not found or readable."
419
- puts "To generate the keys for the first time run: rails generate symmetric_encryption:new_keys\n\n"
420
- return
421
- end
422
- config[:key] = rsa.private_decrypt(encrypted_key)
423
- end
424
-
425
- if iv_filename = config.delete(:iv_filename)
426
- raise(SymmetricEncryption::ConfigError, "Missing mandatory config parameter :private_rsa_key when :iv_filename is supplied") unless rsa
427
- encrypted_iv = begin
428
- File.open(iv_filename, 'rb'){|f| f.read} if iv_filename
429
- rescue Errno::ENOENT
430
- puts "\nSymmetric Encryption initialization vector file: '#{iv_filename}' not found or readable."
431
- puts "To generate the keys for the first time run: rails generate symmetric_encryption:new_keys\n\n"
432
- return
433
- end
434
- config[:iv] = rsa.private_decrypt(encrypted_iv)
435
- end
436
-
437
- if encrypted_key = config.delete(:encrypted_key)
438
- raise(SymmetricEncryption::ConfigError, "Missing mandatory config parameter :private_rsa_key when :encrypted_key is supplied") unless rsa
439
- # Decode value first using encoding specified
440
- encrypted_key = ::Base64.decode64(encrypted_key)
441
- if !encrypted_key || encrypted_key.empty?
442
- puts "\nSymmetric Encryption encrypted_key not found."
443
- puts "To generate the keys for the first time run: rails generate symmetric_encryption:new_keys\n\n"
444
- return
445
- end
446
- config[:key] = rsa.private_decrypt(encrypted_key)
447
- end
448
-
449
- if encrypted_iv = config.delete(:encrypted_iv)
450
- raise(SymmetricEncryption::ConfigError, "Missing mandatory config parameter :private_rsa_key when :encrypted_iv is supplied") unless rsa
451
- # Decode value first using encoding specified
452
- encrypted_iv = ::Base64.decode64(encrypted_iv)
453
- if !encrypted_key || encrypted_key.empty?
454
- puts "\nSymmetric Encryption encrypted_iv not found."
455
- puts "To generate the keys for the first time run: rails generate symmetric_encryption:new_keys\n\n"
456
- return
457
- end
458
- config[:iv] = rsa.private_decrypt(encrypted_iv)
459
- end
460
-
461
- # Backward compatibility
462
- if old_key_name_cipher = config.delete(:cipher)
463
- config[:cipher_name] = old_key_name_cipher
464
- end
465
-
466
- # Decrypt Symmetric Keys
467
- Cipher.new(config)
468
- end
469
-
470
- # Coerce given value into given type
471
- # Does not coerce json or yaml values
472
- def self.coerce(value, type, from_type=nil)
473
- return if value.nil? || (value.is_a?(String) && (value !~ /[^[:space:]]/))
474
-
475
- from_type ||= value.class
476
- case type
477
- when :json
478
- value
479
- when :yaml
480
- value
481
- else
482
- coercer = Coercible::Coercer.new
483
- coercer[from_type].send("to_#{type}".to_sym, value)
484
- end
485
- end
486
-
487
- # Uses coercible gem to coerce values from strings into the target type
488
- # Note: if the type is :string, then the value is returned as is, and the
489
- # coercible gem is not used at all.
490
- def self.coerce_from_string(value, type)
491
- return if value.nil?
492
- case type
493
- when :string
494
- value
495
- when :json
496
- JSON.load(value)
497
- when :yaml
498
- YAML.load(value)
499
- else
500
- self.coerce(value, type, String)
501
- end
502
- end
503
-
504
- # Uses coercible gem to coerce values to strings from the specified type
505
- # Note: if the type is :string, and value is not nil, then #to_s is called
506
- # on the value and the coercible gem is not used at all.
507
- def self.coerce_to_string(value, type)
508
- return if value.nil?
509
-
510
- case type
511
- when :string
512
- value.to_s
513
- when :json
514
- value.to_json
515
- when :yaml
516
- value.to_yaml
517
- else
518
- self.coerce(value, :string, coercion_type(type, value))
519
- end
520
- end
521
-
522
- # Returns the correct coercion type to use for the specified symbol and value
523
- def self.coercion_type(symbol, value)
524
- if symbol == :boolean
525
- value.class
526
- else
527
- COERCION_TYPE_MAP[symbol]
528
- end
529
- end
530
-
531
- COERCION_TYPE_MAP = {
532
- string: String,
533
- integer: Integer,
534
- float: Float,
535
- decimal: BigDecimal,
536
- datetime: DateTime,
537
- time: Time,
538
- date: Date
539
- }
540
-
541
- # With Ruby 1.9 strings have encodings
542
- if defined?(Encoding)
543
- BINARY_ENCODING = Encoding.find("binary")
544
- UTF8_ENCODING = Encoding.find("UTF-8")
545
- end
546
-
290
+ BINARY_ENCODING = Encoding.find('binary')
291
+ UTF8_ENCODING = Encoding.find('UTF-8')
547
292
  end