module AttrEncrypted::InstanceMethods
Public Instance Methods
decrypt(attribute, encrypted_value)
click to toggle source
Decrypts a value for the attribute specified using options evaluated in the current object's scope
Example
class User attr_accessor :secret_key attr_encrypted :email, key: :secret_key def initialize(secret_key) self.secret_key = secret_key end end @user = User.new('some-secret-key') @user.decrypt(:email, 'SOME_ENCRYPTED_EMAIL_STRING')
# File lib/attr_encrypted.rb, line 328 def decrypt(attribute, encrypted_value) encrypted_attributes[attribute.to_sym][:operation] = :decrypting encrypted_attributes[attribute.to_sym][:value_present] = self.class.not_empty?(encrypted_value) begin self.class.decrypt(attribute, encrypted_value, evaluated_attr_encrypted_options_for(attribute)) rescue OpenSSL::Cipher::CipherError => e # When decryption fails with `key:` and the attribute is # configured to attempt a key rotation, let's try to decrypt # with the `old_key:` then rotate the value using the # `rotation_handler:` options = evaluated_attr_encrypted_options_for(attribute) raise e if nil == options[:old_key] || nil == options[:rotation_handler] # but even this may fail if the column's data is encrypted with # neither of these keys, or is corrupted in some way. We need to # catch this scenario and optionally give the host application # the ability to handle unrecoverable data begin value = self.class.decrypt( attribute, encrypted_value, options.merge( key: options[:old_key], iv: options[:old_iv] ) ) handler = options[:rotation_handler] handler.new(self, attribute, value, encrypted_value, options).call value rescue OpenSSL::Cipher::CipherError => e raise e unless options[:rotation_error_handler].present? error_handler = options[:rotation_error_handler] error_handler.new(self, attribute, e, encrypted_value, options).call end end end
encrypt(attribute, value)
click to toggle source
Encrypts a value for the attribute specified using options evaluated in the current object's scope
Example
class User attr_accessor :secret_key attr_encrypted :email, key: :secret_key def initialize(secret_key) self.secret_key = secret_key end end @user = User.new('some-secret-key') @user.encrypt(:email, 'test@example.com')
# File lib/attr_encrypted.rb, line 383 def encrypt(attribute, value) encrypted_attributes[attribute.to_sym][:operation] = :encrypting encrypted_attributes[attribute.to_sym][:value_present] = self.class.not_empty?(value) self.class.encrypt(attribute, value, evaluated_attr_encrypted_options_for(attribute)) end
encrypted_attributes()
click to toggle source
Copies the class level hash of encrypted attributes with virtual attribute names as keys and their corresponding options as values to the instance
# File lib/attr_encrypted.rb, line 392 def encrypted_attributes @encrypted_attributes ||= begin duplicated= {} self.class.encrypted_attributes.map { |key, value| duplicated[key] = value.dup } duplicated end end
Protected Instance Methods
decode_salt_if_encoded(salt, encoding)
click to toggle source
# File lib/attr_encrypted.rb, line 488 def decode_salt_if_encoded(salt, encoding) prefix = '_' salt.slice(0).eql?(prefix) ? salt.slice(1..-1).unpack(encoding).first : salt end
evaluate_attr_encrypted_option(option)
click to toggle source
Evaluates symbol (method reference) or proc (responds to call) options
If the option is not a symbol or proc then the original option is returned
# File lib/attr_encrypted.rb, line 434 def evaluate_attr_encrypted_option(option) if option.is_a?(Symbol) && respond_to?(option, true) send(option) elsif option.respond_to?(:call) option.call(self) else option end end
evaluated_attr_encrypted_options_for(attribute)
click to toggle source
Returns attr_encrypted options evaluated in the current object's scope for the attribute specified
# File lib/attr_encrypted.rb, line 403 def evaluated_attr_encrypted_options_for(attribute) evaluated_options = Hash.new attributes = encrypted_attributes[attribute.to_sym] attribute_option_value = attributes[:attribute] [:if, :unless, :value_present, :allow_empty_value].each do |option| evaluated_options[option] = evaluate_attr_encrypted_option(attributes[option]) end evaluated_options[:attribute] = attribute_option_value evaluated_options.tap do |options| if options[:if] && !options[:unless] && options[:value_present] || options[:allow_empty_value] (attributes.keys - evaluated_options.keys).each do |option| options[option] = evaluate_attr_encrypted_option(attributes[option]) end unless options[:mode] == :single_iv_and_salt load_iv_for_attribute(attribute, options) end if options[:mode] == :per_attribute_iv_and_salt load_salt_for_attribute(attribute, options) end end end end
generate_iv(algorithm)
click to toggle source
# File lib/attr_encrypted.rb, line 462 def generate_iv(algorithm) algo = OpenSSL::Cipher.new(algorithm) algo.encrypt algo.random_iv end
load_iv_for_attribute(attribute, options)
click to toggle source
# File lib/attr_encrypted.rb, line 444 def load_iv_for_attribute(attribute, options) encrypted_attribute_name = options[:attribute] encode_iv = options[:encode_iv] iv = options[:iv] || send("#{encrypted_attribute_name}_iv") if options[:operation] == :encrypting begin iv = generate_iv(options[:algorithm]) iv = [iv].pack(encode_iv) if encode_iv send("#{encrypted_attribute_name}_iv=", iv) rescue RuntimeError end end if iv && !iv.empty? iv = iv.unpack(encode_iv).first if encode_iv options[:iv] = iv end end
load_salt_for_attribute(attribute, options)
click to toggle source
# File lib/attr_encrypted.rb, line 468 def load_salt_for_attribute(attribute, options) encrypted_attribute_name = options[:attribute] encode_salt = options[:encode_salt] salt = options[:salt] || send("#{encrypted_attribute_name}_salt") if options[:operation] == :encrypting salt = SecureRandom.random_bytes salt = prefix_and_encode_salt(salt, encode_salt) if encode_salt send("#{encrypted_attribute_name}_salt=", salt) end if salt && !salt.empty? salt = decode_salt_if_encoded(salt, encode_salt) if encode_salt options[:salt] = salt end end
prefix_and_encode_salt(salt, encoding)
click to toggle source
# File lib/attr_encrypted.rb, line 483 def prefix_and_encode_salt(salt, encoding) prefix = '_' prefix + [salt].pack(encoding) end