Since Rails 7 you are able to encrypt database information with Active Record Show archive.org snapshot . Using Active Record Encryption will store an attribute as string in the database. And uses JSON for serializing the encrypted attribute.
Example:
- 
p: Payload
- 
h: Headers
- 
iv: Initialization Vector
- 
at: Authentication Tag
{ "p": "n7J0/ol+a7DRMeaE", "h": { "iv": "DXZMDWUKfp3bg/Yu", "at": "X1/YjMHbHD4talgF9dt61A=="} }
Note this before encrypting attributes with Active Record:
- You need to choose between a non-deterministicanddeterministicmode. Thenon-deterministicmode is the default and will result in a different payload for the same value. In case you need auniqueindex e.g. for aemailattribute, you need to use thedeterministicmode.
- Encrypting attributes will prevent you from using usual database queries. Searching for substrings, grouping by an attribute, bulk updates and many more. Regular where-Queries will work fordeterministiccolumns.
Example integration with devise
Here is an example encrypting a user table managed by devise Show archive.org snapshot . It could help you to get a rough idea of the necessary steps you need to consider when introduction Active Record Encryption in your application.
class User < ApplicationRecord
  devise :database_authenticatable,
    :registerable,
    :recoverable,
    :rememberable,
    :confirmable
  validates :name, presence: true
  encrypts :email, deterministic: true, downcase: true
  encrypts :unconfirmed_email, deterministic: true, downcase: true
  encrypts :name
end
Using a secrets.yml instead of the credential store
You need to configure your Active Record Encryption keys manually in the config/application.rb:
config.active_record.encryption.primary_key = Rails.application.secrets.dig(:active_record_encryption, :primary_key)
config.active_record.encryption.deterministic_key = Rails.application.secrets.dig(:active_record_encryption, :deterministic_key)
config.active_record.encryption.key_derivation_salt = Rails.application.secrets.dig(:active_record_encryption, :key_derivation_salt)
Your config/secrets.yml can be filled with the values from bin/rails db:encryption:init for each environment.
active_record_encryption:
  primary_key: <some-secret>
  deterministic_key: <some-secret>
  key_derivation_salt: <some-secret>
Migrating existing users
If you already have values in your user database, a good default is to enforce all existing data to be encrypted. Another option would be to encrypt attributes on the fly, when a record is updated.
class EncryptUserAttributes < ActiveRecord::Migration[7.0]
  # It would have been possible to use the EncryptableRecord API with a `previous` option:
  #
  # ```
  # encrypts :email, deterministic: true, downcase: true, previous: { encryptor: ActiveRecord::Encryption::NullEncryptor.new }
  # ```
  #
  # But in the down migration it was difficult to use the Encryption::NullEncryptor as main encryptor and Encryption::Encryptor
  # as previous encryptor. The NullEncryptor will never raise an Encryption::Errors::Base since an encrypted string would still
  # be valid for the NullEncryptor and therefore the previous encrypted is never called.
  #
  # In this approach the implementation of a smart NullEncryptor was skipped and the relevant parts from EncryptableRecord
  # where extracted to reduce the amount of Rails magic.
  class User < ActiveRecord::Base
  end
  def deterministic_key
    ActiveRecord::Encryption.config.deterministic_key
  end
  def up
    User.find_each do |user|
     user.name = ActiveRecord::Encryption::Encryptor.new.encrypt(user.name)
      user.email = ActiveRecord::Encryption::Encryptor.new.encrypt(
        user.email,
        key_provider: ActiveRecord::Encryption::DeterministicKeyProvider.new(deterministic_key),
        cipher_options: { deterministic: true },
      )
      if user.unconfirmed_email.is_a?(String)
        user.unconfirmed_email = ActiveRecord::Encryption::Encryptor.new.encrypt(
          user.unconfirmed_email,
          key_provider: ActiveRecord::Encryption::DeterministicKeyProvider.new(deterministic_key),
          cipher_options: { deterministic: true },
        )
      end
      user.save!
    end
  end
  def down
    User.find_each do |user|
      user.name = ActiveRecord::Encryption::Encryptor.new.decrypt(user.name)
      user.email = ActiveRecord::Encryption::Encryptor.new.decrypt(
        user.email,
        key_provider: ActiveRecord::Encryption::DeterministicKeyProvider.new(deterministic_key),
      )
      if user.unconfirmed_email.is_a?(String)
        user.unconfirmed_email = ActiveRecord::Encryption::Encryptor.new.decrypt(
          user.unconfirmed_email,
          key_provider: ActiveRecord::Encryption::DeterministicKeyProvider.new(deterministic_key),
        )
      end
      user.save!
    end
  end
end
Update the database constraints of devise
You might want to remove the default: '' option from the email column (see 
  #5552
  
    Show archive.org snapshot
  
 & 
  #5436
  
    Show archive.org snapshot
  
 for the discussions). In case you don't use omniauth, adding a null: false constraint could improve your data integrity.
class ChangeUserContraints < ActiveRecord::Migration[7.0]
  def up
    change_column_default(:users, :email, nil)
    change_column_null(:users, :email, false)
  end
  def down
    change_column_null(:users, :email, true)
    change_column_default(:users, :email, '')
  end
end
Optional adjusting SimpleForm
In case you are using SimpleForm Show archive.org snapshot , you might notice that the inputs are not correctly detected as string anymore. This might be fixed in the future, for now declaring the type solved the issue.
    = form.input :email,
      required: true,
      autofocus: true,
-     input_html: { autocomplete: 'email' }
+     input_html: { autocomplete: 'email' },
+     as: :string