lib/attr_encrypted.rb in powerhome-attr_encrypted-1.0.1 vs lib/attr_encrypted.rb in powerhome-attr_encrypted-1.1.0
- old
+ new
@@ -234,11 +234,11 @@
# attr_encrypted :email
# end
#
# email = User.decrypt(:email, 'SOME_ENCRYPTED_EMAIL_STRING')
def decrypt(attribute, encrypted_value, options = {})
- options = encrypted_attributes[attribute.to_sym].merge(options)
+ options = encrypted_attributes[attribute.to_sym].merge(options).compact
if options[:if] && !options[:unless] && not_empty?(encrypted_value)
encrypted_value = encrypted_value.unpack(options[:encode]).first if options[:encode]
value = options[:encryptor].send(options[:decrypt_method], options.merge!(value: encrypted_value))
if options[:marshal]
value = options[:marshaler].send(options[:load_method], value)
@@ -326,10 +326,44 @@
# @user = User.new('some-secret-key')
# @user.decrypt(:email, 'SOME_ENCRYPTED_EMAIL_STRING')
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)
- self.class.decrypt(attribute, encrypted_value, evaluated_attr_encrypted_options_for(attribute))
+ 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
# Encrypts a value for the attribute specified using options evaluated in the current object's scope
#
# Example