This is the concern mixed in Active Record models to make them encryptable. It adds the encrypts attribute declaration, as well as the API to encrypt and decrypt records.

Methods

Constants

ORIGINAL_ATTRIBUTE_PREFIX = "original_"

Instance Public methods

add_length_validation_for_encrypted_columns()

πŸ“ Source code
# File activerecord/lib/active_record/encryption/encryptable_record.rb, line 127
          def add_length_validation_for_encrypted_columns
            encrypted_attributes&.each do |attribute_name|
              validate_column_size attribute_name
            end
          end
πŸ”Ž See on GitHub

ciphertext_for(attribute_name)

Returns the ciphertext for attribute_name.

πŸ“ Source code
# File activerecord/lib/active_record/encryption/encryptable_record.rb, line 146
      def ciphertext_for(attribute_name)
        read_attribute_before_type_cast(attribute_name)
      end
πŸ”Ž See on GitHub

decrypt()

Decrypts all the encryptable attributes and saves the changes.

πŸ“ Source code
# File activerecord/lib/active_record/encryption/encryptable_record.rb, line 156
      def decrypt
        decrypt_attributes if has_encrypted_attributes?
      end
πŸ”Ž See on GitHub

deterministic_encrypted_attributes()

Returns the list of deterministic encryptable attributes in the model class.

πŸ“ Source code
# File activerecord/lib/active_record/encryption/encryptable_record.rb, line 56
        def deterministic_encrypted_attributes
          @deterministic_encrypted_attributes ||= encrypted_attributes&.find_all do |attribute_name|
            type_for_attribute(attribute_name).deterministic?
          end
        end
πŸ”Ž See on GitHub

encrypt()

Encrypts all the encryptable attributes and saves the changes.

πŸ“ Source code
# File activerecord/lib/active_record/encryption/encryptable_record.rb, line 151
      def encrypt
        encrypt_attributes if has_encrypted_attributes?
      end
πŸ”Ž See on GitHub

encrypt_attribute(name, attribute_scheme)

πŸ“ Source code
# File activerecord/lib/active_record/encryption/encryptable_record.rb, line 82
          def encrypt_attribute(name, attribute_scheme)
            encrypted_attributes << name.to_sym

            attribute name do |cast_type|
              ActiveRecord::Encryption::EncryptedAttributeType.new scheme: attribute_scheme, cast_type: cast_type
            end

            preserve_original_encrypted(name) if attribute_scheme.ignore_case?
            ActiveRecord::Encryption.encrypted_attribute_was_declared(self, name)
          end
πŸ”Ž See on GitHub

encrypted_attribute?(attribute_name)

Returns whether a given attribute is encrypted or not.

πŸ“ Source code
# File activerecord/lib/active_record/encryption/encryptable_record.rb, line 141
      def encrypted_attribute?(attribute_name)
        ActiveRecord::Encryption.encryptor.encrypted? ciphertext_for(attribute_name)
      end
πŸ”Ž See on GitHub

encrypts(*names, key_provider: nil, key: nil, deterministic: false, downcase: false, ignore_case: false, previous: [], **context_properties)

Encrypts the name attribute.

Options

  • :key_provider - A key provider to provide encryption and decryption keys. Defaults to ActiveRecord::Encryption.key_provider.

  • :key - A password to derive the key from. It’s a shorthand for a :key_provider that serves derivated keys. Both options can’t be used at the same time.

  • :deterministic - By default, encryption is not deterministic. It will use a random initialization vector for each encryption operation. This means that encrypting the same content with the same key twice will generate different ciphertexts. When set to true, it will generate the initialization vector based on the encrypted content. This means that the same content will generate the same ciphertexts. This enables querying encrypted text with Active Record. Deterministic encryption will use the oldest encryption scheme to encrypt new data by default. You can change this by setting +deterministic: { fixed: false }+. That will make it use the newest encryption scheme for encrypting new data.

  • :downcase - When true, it converts the encrypted content to downcase automatically. This allows to effectively ignore case when querying data. Notice that the case is lost. Use :ignore_case if you are interested in preserving it.

  • :ignore_case - When true, it behaves like :downcase but, it also preserves the original case in a specially designated column +original_<name>+. When reading the encrypted content, the version with the original case is served. But you can still execute queries that will ignore the case. This option can only be used when :deterministic is true.

  • :context_properties - Additional properties that will override Context settings when this attribute is encrypted and decrypted. E.g: encryptor:, cipher:, message_serializer:, etc.

  • :previous - List of previous encryption schemes. When provided, they will be used in order when trying to read the attribute. Each entry of the list can contain the properties supported by encrypts. Also, when deterministic encryption is used, they will be used to generate additional ciphertexts to check in the queries.

πŸ“ Source code
# File activerecord/lib/active_record/encryption/encryptable_record.rb, line 45
        def encrypts(*names, key_provider: nil, key: nil, deterministic: false, downcase: false, ignore_case: false, previous: [], **context_properties)
          self.encrypted_attributes ||= Set.new # not using :default because the instance would be shared across classes
          scheme = scheme_for key_provider: key_provider, key: key, deterministic: deterministic, downcase: downcase, \
              ignore_case: ignore_case, previous: previous, **context_properties

          names.each do |name|
            encrypt_attribute name, scheme
          end
        end
πŸ”Ž See on GitHub

global_previous_schemes_for(scheme)

πŸ“ Source code
# File activerecord/lib/active_record/encryption/encryptable_record.rb, line 76
          def global_previous_schemes_for(scheme)
            ActiveRecord::Encryption.config.previous_schemes.collect do |previous_scheme|
              scheme.merge(previous_scheme)
            end
          end
πŸ”Ž See on GitHub

load_schema!()

πŸ“ Source code
# File activerecord/lib/active_record/encryption/encryptable_record.rb, line 121
          def load_schema!
            super

            add_length_validation_for_encrypted_columns if ActiveRecord::Encryption.config.validate_column_size
          end
πŸ”Ž See on GitHub

override_accessors_to_preserve_original(name, original_attribute_name)

πŸ“ Source code
# File activerecord/lib/active_record/encryption/encryptable_record.rb, line 104
          def override_accessors_to_preserve_original(name, original_attribute_name)
            include(Module.new do
              define_method name do
                if ((value = super()) && encrypted_attribute?(name)) || !ActiveRecord::Encryption.config.support_unencrypted_data
                  send(original_attribute_name)
                else
                  value
                end
              end

              define_method "#{name}=" do |value|
                self.send "#{original_attribute_name}=", value
                super(value)
              end
            end)
          end
πŸ”Ž See on GitHub

preserve_original_encrypted(name)

πŸ“ Source code
# File activerecord/lib/active_record/encryption/encryptable_record.rb, line 93
          def preserve_original_encrypted(name)
            original_attribute_name = "#{ORIGINAL_ATTRIBUTE_PREFIX}#{name}".to_sym

            if !ActiveRecord::Encryption.config.support_unencrypted_data && !column_names.include?(original_attribute_name.to_s)
              raise Errors::Configuration, "To use :ignore_case for '#{name}' you must create an additional column named '#{original_attribute_name}'"
            end

            encrypts original_attribute_name
            override_accessors_to_preserve_original name, original_attribute_name
          end
πŸ”Ž See on GitHub

scheme_for(key_provider: nil, key: nil, deterministic: false, downcase: false, ignore_case: false, previous: [], **context_properties)

πŸ“ Source code
# File activerecord/lib/active_record/encryption/encryptable_record.rb, line 68
          def scheme_for(key_provider: nil, key: nil, deterministic: false, downcase: false, ignore_case: false, previous: [], **context_properties)
            ActiveRecord::Encryption::Scheme.new(key_provider: key_provider, key: key, deterministic: deterministic,
                                                 downcase: downcase, ignore_case: ignore_case, **context_properties).tap do |scheme|
              scheme.previous_schemes = global_previous_schemes_for(scheme) +
                Array.wrap(previous).collect { |scheme_config| ActiveRecord::Encryption::Scheme.new(**scheme_config) }
            end
          end
πŸ”Ž See on GitHub

source_attribute_from_preserved_attribute(attribute_name)

Given a attribute name, it returns the name of the source attribute when it’s a preserved one.

πŸ“ Source code
# File activerecord/lib/active_record/encryption/encryptable_record.rb, line 63
        def source_attribute_from_preserved_attribute(attribute_name)
          attribute_name.to_s.sub(ORIGINAL_ATTRIBUTE_PREFIX, "") if /^#{ORIGINAL_ATTRIBUTE_PREFIX}/.match?(attribute_name)
        end
πŸ”Ž See on GitHub

validate_column_size(attribute_name)

πŸ“ Source code
# File activerecord/lib/active_record/encryption/encryptable_record.rb, line 133
          def validate_column_size(attribute_name)
            if limit = columns_hash[attribute_name.to_s]&.limit
              validates_length_of attribute_name, maximum: limit
            end
          end
πŸ”Ž See on GitHub