If you're hosting your web server in a particular region, it might be necessary to comply with the GDPR norms of that region. Anonymizing and encrypting data becomes necessary in such situations. In this blog, we will discuss the attribute encryption that Rails 7 provides right out of the box. And we will also see the Deterministic & Non Deterministic approaches. (If you're using Rails version lesser than 7, check out our previous blog here on how to write a custom encryption framework.)

ActiveRecord attribute encryption in Ruby on Rails for better security
In this blog post, we will explore a quick way to encrypt & decrypt model attributes using Rails handy ActiveSupport::MessageEncryptor class to ensure

Apart from this, encryption also helps protect against data breach. Sensitive information like customer's email, phone or home address must be encrypted and filtered at the application level as it adds an additional layer of protection against unauthorized access to the database, application console or server logs.


Installing Rails 7

As of the time of writing this blog, Rails 7 is still in the alpha phase. You can install it by adding this to your Gemfile.

gem 'rails', github: 'rails/rails', branch: 'main'

Run bundle install to install the gem and verify the gem version using the rails -v command.

$ rails -v
Rails 7.0.0.alpha

Voila! You've successfully installed Rails 7.

Initializing Encryption keys

To get started with encryption, first, we'd need to initialize some keys that will be used by the encrypts API to encrypt and decrypt attributes.
Run bin/rails db:encryption:init to generate a random key set.

$ bin/rails db:encryption:init
Add this entry to the credentials of the target environment:

active_record_encryption:
  primary_key: EGY8WhulUOXixybod7ZWwMIL68R9o5kC
  deterministic_key: aPA5XyALhf75NNnMzaspW7akTfZp0lPY
  key_derivation_salt: xEY0dt6TZcAMg52K7O84wYzkjvbA62Hz

Add the generated key set to your RoR application's credentials file.

  • primary_key - Used to derive the root encryption key for non-deterministic encryption.
  • deterministic_key - Used to derive the root encryption key for deterministic encryption.
  • key_derivation_salt - Sequence of bits added to the root key to strengthen it further.

Declaration of Encrypted Attribute

To encrypt any attribute, we simply need to add a declaration encrypts at the model level.

class User
  encrypts :email
end

Deterministic & Non Deterministic approach

By default, Rails uses a non-deterministic (also known as probabilistic) approach to encrypt the plain text. Under the hood, it uses a random initialization vector to encrypt the plain text. Due to this, the same plain text will generate a different cipher each time it is encrypted making it harder for the attacker to find patterns in the encrypted data and eventually guessing the key.

But wait a minute. Querying non-deterministically encrypted data is impossible now.

User.create email: 'jon.doe@example.com'
# => <User:0x00 id: 1, email: "jon.doe@example.com"...>

User.find_by email: 'jon.doe@example.com'
# => nil

If you want to directly query an encrypted column attribute, you'd need to use the deterministic approach. For this, simply use the deterministic: true option during declaration.

class User
  encrypts :email, deterministic: true
end

User.create email: 'jon.doe@example.com'
# => <User:0x00 id: 1, email: "jon.doe@example.com"...>

User.find_by email: 'jon.doe@example.com'
# => <User:0x00 id: 1, email: "jon.doe@example.com"...>

Under the hood, rails will use a fixed initialization vector for encryption. As you might've already guessed, this is not very secure. Hence, it is recommended to use non-deterministic approach if querying the column is not required.

Encryption Key Management

Encryption Key Rotation

ActiveRecord Encryption allows support for key rotation by providing a list of keys to active_record.encryption.primary_key

active_record:
  encryption:
    primary_key:
      - a1cc4d7b9f420e40a337b9e68c5ecec6 # Used for decryption only
      - bc17e7b413fd4720716a7633027f8cc4 # Used for encryption & decryption

From the above list of primary keys, the last key will always be used for encryption and all listed keys will be used for decryption until one of them works.

Decryption can be made more performant by storing the reference id of the encryption key in the encrypted message itself.

config.active_record.encryption.store_key_references = true

Now, the decryption key can be accessed directly without having to try out every key to find out which one works. Neat!

Note: Key rotation is not supported for deterministic encryption as of yet.

Built-in Encryption Key Providers

ActiveRecord Encryption further lets you control the key management strategies by allowing you to choose between different key providers.

Rails 7 comes with two built-in key providers to choose from:

  • DerivedSecretKeyProvider (default) :
    Serves encryption & decryption keys derived from the provided passwords using PBKDF2.

  • EnvelopeEncryptionKeyProvider :
    The envelope encryption strategy uses a random secret to encrypt the plain text. The random secret is then encrypted using the encryption key and included in the encrypted message's headers. You can enable this by setting the following in your application.rb.

    config.active_record.encryption.key_provider = ActiveRecord::Encryption::EnvelopeEncryptionKeyProvider.new
    

ActiveRecord Encryption has tons of other configurations which are worth checking out. You can refer to the Rails edge guides to find out more about them. Hope you find this blog helpful. Thank you for reading!

References