lib/symmetric_encryption/symmetric_encryption.rb in symmetric-encryption-3.7.2 vs lib/symmetric_encryption/symmetric_encryption.rb in symmetric-encryption-3.8.0
- old
+ new
@@ -8,13 +8,13 @@
# The symmetric key is protected using the private key below and must
# be distributed separately from the application
module SymmetricEncryption
# Defaults
- @@cipher = nil
+ @@cipher = nil
@@secondary_ciphers = []
- @@select_cipher = nil
+ @@select_cipher = nil
# List of types supported when encrypting or decrypting data
#
# Each type maps to the built-in Ruby types as follows:
# :string => String
@@ -24,11 +24,11 @@
# :datetime => DateTime
# :time => Time
# :date => Date
# :json => Uses JSON serialization, useful for hashes and arrays
# :yaml => Uses YAML serialization, useful for hashes and arrays
- COERCION_TYPES = [:string, :integer, :float, :decimal, :datetime, :time, :date, :boolean, :json, :yaml]
+ COERCION_TYPES = [:string, :integer, :float, :decimal, :datetime, :time, :date, :boolean, :json, :yaml]
# Set the Primary Symmetric Cipher to be used
#
# Example: For testing purposes the following test cipher can be used:
#
@@ -47,23 +47,23 @@
# Returns the primary cipher if no match was found and version == 0
# Returns nil if no match was found and version != 0
def self.cipher(version = nil)
raise(SymmetricEncryption::ConfigError, 'Call SymmetricEncryption.load! or SymmetricEncryption.cipher= prior to encrypting or decrypting data') unless @@cipher
return @@cipher if version.nil? || (@@cipher.version == version)
- secondary_ciphers.find {|c| c.version == version} || (@@cipher if version == 0)
+ secondary_ciphers.find { |c| c.version == version } || (@@cipher if version == 0)
end
# Returns whether a primary cipher has been set
def self.cipher?
!@@cipher.nil?
end
# Set the Secondary Symmetric Ciphers Array to be used
def self.secondary_ciphers=(secondary_ciphers)
- raise(ArgumentError, "secondary_ciphers must be a collection") unless secondary_ciphers.respond_to? :each
+ raise(ArgumentError, 'secondary_ciphers must be a collection') unless secondary_ciphers.respond_to? :each
secondary_ciphers.each do |cipher|
- raise(ArgumentError, "secondary_ciphers can only consist of SymmetricEncryption::Ciphers") unless cipher.respond_to?(:encrypt) && cipher.respond_to?(:decrypt)
+ raise(ArgumentError, 'secondary_ciphers can only consist of SymmetricEncryption::Ciphers') unless cipher.respond_to?(:encrypt) && cipher.respond_to?(:decrypt)
end
@@secondary_ciphers = secondary_ciphers
end
# Returns the Primary Symmetric Cipher being used
@@ -107,32 +107,31 @@
# successfully returns a string of data
def self.decrypt(encrypted_and_encoded_string, version=nil, type=:string)
raise(SymmetricEncryption::ConfigError, 'Call SymmetricEncryption.load! or SymmetricEncryption.cipher= prior to encrypting or decrypting data') unless @@cipher
return encrypted_and_encoded_string if encrypted_and_encoded_string.nil? || (encrypted_and_encoded_string == '')
- str = encrypted_and_encoded_string.to_s
+ str = encrypted_and_encoded_string.to_s
# Decode before decrypting supplied string
decoded = @@cipher.decode(str)
return unless decoded
return decoded if decoded.empty?
- decrypted = if header = Cipher.parse_header!(decoded)
- header.decryption_cipher.binary_decrypt(decoded, header)
- else
- # Use cipher_selector if present to decide which cipher to use
- c = @@select_cipher.nil? ? cipher(version) : @@select_cipher.call(str, decoded)
- c.binary_decrypt(decoded)
- end
-
- if defined?(Encoding)
- # Try to force result to UTF-8 encoding, but if it is not valid, force it back to Binary
- unless decrypted.force_encoding(SymmetricEncryption::UTF8_ENCODING).valid_encoding?
- decrypted.force_encoding(SymmetricEncryption::BINARY_ENCODING)
+ decrypted =
+ if header = Cipher.parse_header!(decoded)
+ header.decryption_cipher.binary_decrypt(decoded, header)
+ else
+ # Use cipher_selector if present to decide which cipher to use
+ c = @@select_cipher.nil? ? cipher(version) : @@select_cipher.call(str, decoded)
+ c.binary_decrypt(decoded)
end
+
+ # Try to force result to UTF-8 encoding, but if it is not valid, force it back to Binary
+ unless decrypted.force_encoding(SymmetricEncryption::UTF8_ENCODING).valid_encoding?
+ decrypted.force_encoding(SymmetricEncryption::BINARY_ENCODING)
end
- coerce_from_string(decrypted, type)
+ Coerce.coerce_from_string(decrypted, type)
end
# AES Symmetric Encryption of supplied string
# Returns result as a Base64 encoded string
# Returns nil if the supplied str is nil
@@ -177,11 +176,11 @@
# Default: :string
def self.encrypt(str, random_iv=false, compress=false, type=:string)
raise(SymmetricEncryption::ConfigError, 'Call SymmetricEncryption.load! or SymmetricEncryption.cipher= prior to encrypting or decrypting data') unless @@cipher
# Encrypt and then encode the supplied string
- @@cipher.encrypt(coerce_to_string(str, type), random_iv, compress)
+ @@cipher.encrypt(Coerce.coerce_to_string(str, type), random_iv, compress)
end
# Invokes decrypt
# Returns decrypted String
# Return nil if it fails to decrypt a String
@@ -253,295 +252,41 @@
# Default: Rails.root/config/symmetric-encryption.yml
# environment:
# Which environments config to load. Usually: production, development, etc.
# Default: Rails.env
def self.load!(filename=nil, environment=nil)
- ciphers = read_config(filename, environment)
- @@cipher = ciphers.shift
- @@secondary_ciphers = ciphers
- true
+ Config.load!(filename, environment)
end
# Generate new random symmetric keys for use with this Encryption library
#
# Note: Only the current Encryption key settings are used
#
# Creates Symmetric Key .key
- # and initilization vector .iv
+ # and initialization vector .iv
# which is encrypted with the above Public key
#
# Existing key files will be renamed if present
def self.generate_symmetric_key_files(filename=nil, environment=nil)
- config_filename = filename || File.join(Rails.root, "config", "symmetric-encryption.yml")
- config = YAML.load(ERB.new(File.new(config_filename).read).result)[environment || Rails.env]
+ config = Config.read_config(filename, environment)
- # RSA key to decrypt key files
- private_rsa_key = config.delete('private_rsa_key')
- raise(SymmetricEncryption::ConfigError, "The configuration file must contain a 'private_rsa_key' parameter to generate symmetric keys") unless private_rsa_key
- rsa_key = OpenSSL::PKey::RSA.new(private_rsa_key)
-
- # Check if config file contains 1 or multiple ciphers
- ciphers = config.delete('ciphers')
- cfg = ciphers.nil? ? config : ciphers.first
-
- # Convert keys to symbols
- cipher_cfg = {}
- cfg.each_pair{|k,v| cipher_cfg[k.to_sym] = v}
-
- cipher_name = cipher_cfg[:cipher_name] || cipher_cfg[:cipher]
-
- # Generate a new Symmetric Key pair
- iv_filename = cipher_cfg[:iv_filename]
- key_pair = SymmetricEncryption::Cipher.random_key_pair(cipher_name || 'aes-256-cbc')
-
- if key_filename = cipher_cfg[:key_filename]
- # Save symmetric key after encrypting it with the private RSA key, backing up existing files if present
- File.rename(key_filename, "#{key_filename}.#{Time.now.to_i}") if File.exist?(key_filename)
- File.open(key_filename, 'wb') {|file| file.write( rsa_key.public_encrypt(key_pair[:key]) ) }
- puts("Generated new Symmetric Key for encryption. Please copy #{key_filename} to the other web servers in #{environment}.")
- elsif !cipher_cfg[:key]
- key = rsa_key.public_encrypt(key_pair[:key])
- puts "Generated new Symmetric Key for encryption. Set the KEY environment variable in #{environment} to:"
- puts ::Base64.encode64(key)
- end
-
- if iv_filename
- File.rename(iv_filename, "#{iv_filename}.#{Time.now.to_i}") if File.exist?(iv_filename)
- File.open(iv_filename, 'wb') {|file| file.write( rsa_key.public_encrypt(key_pair[:iv]) ) }
- puts("Generated new Symmetric Key for encryption. Please copy #{iv_filename} to the other web servers in #{environment}.")
- elsif !cipher_cfg[:iv]
- iv = rsa_key.public_encrypt(key_pair[:iv])
- puts "Generated new Symmetric Key for encryption. Set the IV environment variable in #{environment} to:"
- puts ::Base64.encode64(iv)
- end
+ # Only regenerating the first configured cipher
+ cipher_config = config[:ciphers].first
+ Cipher.generate_random_keys({environment: environment, private_rsa_key: config[:private_rsa_key]}.merge(cipher_config))
end
# Generate a 22 character random password
def self.random_password
Base64.encode64(OpenSSL::Cipher.new('aes-128-cbc').random_key)[0..-4].strip
end
# Binary encrypted data includes this magic header so that we can quickly
# identify binary data versus base64 encoded data that does not have this header
unless defined? MAGIC_HEADER
- MAGIC_HEADER = '@EnC'
- MAGIC_HEADER_SIZE = MAGIC_HEADER.size
+ MAGIC_HEADER = '@EnC'
+ MAGIC_HEADER_SIZE = MAGIC_HEADER.size
MAGIC_HEADER_UNPACK = "a#{MAGIC_HEADER_SIZE}v"
end
- protected
-
- # Returns [Array(SymmetricEncrytion::Cipher)] ciphers specified in the configuration file
- #
- # Read the configuration from the YAML file and return in the latest format
- #
- # filename:
- # Name of file to read.
- # Mandatory for non-Rails apps
- # Default: Rails.root/config/symmetric-encryption.yml
- # environment:
- # Which environments config to load. Usually: production, development, etc.
- def self.read_config(filename=nil, environment=nil)
- config_filename = filename || File.join(Rails.root, "config", "symmetric-encryption.yml")
- config = YAML.load(ERB.new(File.new(config_filename).read).result)[environment || Rails.env]
-
- # RSA key to decrypt key files
- private_rsa_key = config.delete('private_rsa_key')
-
- if ciphers = config.delete('ciphers')
- ciphers.collect {|cipher_conf| cipher_from_config(cipher_conf, private_rsa_key)}
- else
- [cipher_from_config(config, private_rsa_key)]
- end
- end
-
- # Returns an instance of SymmetricEncryption::Cipher created from
- # the supplied configuration and optional rsa_encryption_key
- #
- # Raises an Exception on failure
- #
- # Parameters:
- # cipher_conf Hash:
- # :cipher_name
- # Encryption cipher name for the symmetric encryption key
- #
- # :version
- # The version number of this cipher
- # Default: 0
- #
- # :encoding [Symbol]
- # Encoding to use after encrypting with this cipher
- #
- # :always_add_header
- # Whether to always include the header when encrypting data.
- # Highly recommended to set this value to true.
- # Increases the length of the encrypted data by 6 bytes, but makes
- # migration to a new key trivial
- # Default: false
- #
- # :key
- # The actual key to use for encryption/decryption purposes
- #
- # :key_filename
- # Name of file containing symmetric key encrypted using the public
- # key from the private_rsa_key
- #
- # :encrypted_key
- # Symmetric key encrypted using the public key from the private_rsa_key
- # and then Base64 encoded
- #
- # :iv
- # Optional: The actual iv to use for encryption/decryption purposes
- #
- # :encrypted_iv
- # Initialization vector encrypted using the public key from the private_rsa_key
- # and then Base64 encoded
- #
- # :iv_filename
- # Optional: Name of file containing symmetric key initialization vector
- # encrypted using the public key from the private_rsa_key
- #
- # private_rsa_key [String]
- # RSA Key used to decrypt key and iv as applicable
- def self.cipher_from_config(cipher_conf, private_rsa_key=nil)
- config = {}
- cipher_conf.each_pair{|k,v| config[k.to_sym] = v}
-
- # To decrypt encrypted key or iv files
- rsa = OpenSSL::PKey::RSA.new(private_rsa_key) if private_rsa_key
-
- # Load Encrypted Symmetric keys
- if key_filename = config.delete(:key_filename)
- raise(SymmetricEncryption::ConfigError, "Missing mandatory config parameter :private_rsa_key when :key_filename is supplied") unless rsa
- encrypted_key = begin
- File.open(key_filename, 'rb'){|f| f.read}
- rescue Errno::ENOENT
- puts "\nSymmetric Encryption key file: '#{key_filename}' not found or readable."
- puts "To generate the keys for the first time run: rails generate symmetric_encryption:new_keys\n\n"
- return
- end
- config[:key] = rsa.private_decrypt(encrypted_key)
- end
-
- if iv_filename = config.delete(:iv_filename)
- raise(SymmetricEncryption::ConfigError, "Missing mandatory config parameter :private_rsa_key when :iv_filename is supplied") unless rsa
- encrypted_iv = begin
- File.open(iv_filename, 'rb'){|f| f.read} if iv_filename
- rescue Errno::ENOENT
- puts "\nSymmetric Encryption initialization vector file: '#{iv_filename}' not found or readable."
- puts "To generate the keys for the first time run: rails generate symmetric_encryption:new_keys\n\n"
- return
- end
- config[:iv] = rsa.private_decrypt(encrypted_iv)
- end
-
- if encrypted_key = config.delete(:encrypted_key)
- raise(SymmetricEncryption::ConfigError, "Missing mandatory config parameter :private_rsa_key when :encrypted_key is supplied") unless rsa
- # Decode value first using encoding specified
- encrypted_key = ::Base64.decode64(encrypted_key)
- if !encrypted_key || encrypted_key.empty?
- puts "\nSymmetric Encryption encrypted_key not found."
- puts "To generate the keys for the first time run: rails generate symmetric_encryption:new_keys\n\n"
- return
- end
- config[:key] = rsa.private_decrypt(encrypted_key)
- end
-
- if encrypted_iv = config.delete(:encrypted_iv)
- raise(SymmetricEncryption::ConfigError, "Missing mandatory config parameter :private_rsa_key when :encrypted_iv is supplied") unless rsa
- # Decode value first using encoding specified
- encrypted_iv = ::Base64.decode64(encrypted_iv)
- if !encrypted_key || encrypted_key.empty?
- puts "\nSymmetric Encryption encrypted_iv not found."
- puts "To generate the keys for the first time run: rails generate symmetric_encryption:new_keys\n\n"
- return
- end
- config[:iv] = rsa.private_decrypt(encrypted_iv)
- end
-
- # Backward compatibility
- if old_key_name_cipher = config.delete(:cipher)
- config[:cipher_name] = old_key_name_cipher
- end
-
- # Decrypt Symmetric Keys
- Cipher.new(config)
- end
-
- # Coerce given value into given type
- # Does not coerce json or yaml values
- def self.coerce(value, type, from_type=nil)
- return if value.nil? || (value.is_a?(String) && (value !~ /[^[:space:]]/))
-
- from_type ||= value.class
- case type
- when :json
- value
- when :yaml
- value
- else
- coercer = Coercible::Coercer.new
- coercer[from_type].send("to_#{type}".to_sym, value)
- end
- end
-
- # Uses coercible gem to coerce values from strings into the target type
- # Note: if the type is :string, then the value is returned as is, and the
- # coercible gem is not used at all.
- def self.coerce_from_string(value, type)
- return if value.nil?
- case type
- when :string
- value
- when :json
- JSON.load(value)
- when :yaml
- YAML.load(value)
- else
- self.coerce(value, type, String)
- end
- end
-
- # Uses coercible gem to coerce values to strings from the specified type
- # Note: if the type is :string, and value is not nil, then #to_s is called
- # on the value and the coercible gem is not used at all.
- def self.coerce_to_string(value, type)
- return if value.nil?
-
- case type
- when :string
- value.to_s
- when :json
- value.to_json
- when :yaml
- value.to_yaml
- else
- self.coerce(value, :string, coercion_type(type, value))
- end
- end
-
- # Returns the correct coercion type to use for the specified symbol and value
- def self.coercion_type(symbol, value)
- if symbol == :boolean
- value.class
- else
- COERCION_TYPE_MAP[symbol]
- end
- end
-
- COERCION_TYPE_MAP = {
- string: String,
- integer: Integer,
- float: Float,
- decimal: BigDecimal,
- datetime: DateTime,
- time: Time,
- date: Date
- }
-
- # With Ruby 1.9 strings have encodings
- if defined?(Encoding)
- BINARY_ENCODING = Encoding.find("binary")
- UTF8_ENCODING = Encoding.find("UTF-8")
- end
-
+ BINARY_ENCODING = Encoding.find('binary')
+ UTF8_ENCODING = Encoding.find('UTF-8')
end