Before Rails 7, we used a lot of gems like attr_encrypted to encrypt and decrypt data in Active record models.
Let’s take an example of a User
model,
where we want to store the email in an encrypted format.
class User < ApplicationRecord
validates_presence_of :email
end
Previously, using attr_encrypted
gem we would do something like this:
class User < ApplicationRecord
validates_presence_of :email
attr_encrypted :email, key: :encryption_key
end
This increases the dependency on third-party gem for critical functionality like encryption.
To handle this problem, Rails 7 adds encrypted attributes to ActiveRecord models.
Basic setup
- Firstly, we need to add some keys to our Rails credential file. Run the following command to generate the key set:
$ bin/rails db:encryption:init
- Specify the attributes we need to encrypt. This happens at the model level:
# app/models/user.rb
class User < ApplicationRecord
encrypts :email
validates_presence_of :email
end
Usage
The library will transparently encrypt email
before saving it into the database,
and will decrypt it when retrieving its value.
User.create email: "sam@welcome.com"
# INSERT INTO `users` (`email`) VALUES ('{\"p\":\"n7J0/ol+a7DRMeaE\",\"h\":{\"iv\":\"DXZMDWUKfp3bg/Yu\",\"at\":\"X1/YjMHbHD4talgF9dt61A==\"}}')
Deterministic and Non-Deterministic encryption
- By default, ActiveRecord Encryption uses a
non-deterministic
approach for encryption. That means encrypting the same email twice will result in 2 different ciphertexts. It is better for security purposes, but it makes querying the database impossible. So we can use thedeterministic
approach to resolve this issue.
# app/models/user.rb
class User < ApplicationRecord
encrypts :email, deterministic: true
encrypts :phone_number
end
After this if we query the model normally like:
User.find_by(email: "sam@welcome.com")
User.find_by(phone_number: "9911223344")
Since we did not set deterministic: true
for the phone_number
attribute,
the query fails to find the user.
Custom Encryption methods
Rails 7 uses the EncryptableRecord concern to perform encryption and decryption when saving and retrieving values from the database. The main components of an encryption system are:
- Encryptor - responsible for encrypting/decrypting data.
- Cipher - the encryption algorithm (Aes 256 GCM)
- KeyProvider - serves encryption and decryption keys
- MessageSerializer - in charge of serializing and deserializing encrypted Message.
These components can be customized according to the needs by modifying the respective settings in the config file like:
config.active_record.encryption.encryptor = MyEncryptor.new
For more details, refer to this pull request.