At Zonmaster, we manage a substantial amount of sensitive data, including Personally Identifiable Information (PII), along with other types of data. As part of our agreement with Amazon, we are committed to storing this information securely. While we previously relied on various gems to achieve this, having encryption features built directly into Rails has greatly enhanced our ability to protect this vital information.
Security is a top priority in modern web development
Recently I posted an article about moving some data out of our MySQL database and into S3 files. In that article, I mentioned that these columns were encrypted. Because Zonmaster started out life in 2015 it uses a home-grown encryption solution.
But now Rails 7 has introduced a powerful feature to help developers protect sensitive data: encrypted attributes. This built-in functionality provides an additional layer of security that is both easy to implement and robust.
What Are Encrypted Attributes?
Encrypted attributes allow you to encrypt specific data within your models, ensuring that sensitive information - like passwords or personal details - is stored securely in the database. This feature is included by default in Rails 7, making it accessible to all developers working with this version of the framework.
Getting Started with Encrypted Attributes
Step 1: Initialize Encryption Keys
Before you can begin encrypting attributes, you'll need to generate a set of keys for your Rails credentials. This can be done with the following command:
rails db:encryption:init
This will output something like this:
Add this entry to the credentials of the target environment:
active_record_encryption:
primary_key: 71TCMk9YKXkBJQSnbdQsRW0qdOcjeNEM
deterministic_key: tUJgmCDRZTJKZd35qGBFma2LFdUoH4d5
key_derivation_salt: XI7SAcXcoGdbaZM3zvNp0Wdpm3mx3xqw
As it says, add these to your credentials file using rails credentials:edit
.
Step 2: Declare Encrypted Attributes
Next, you'll need to specify which attributes you want to encrypt within your model. Here's an example of how to declare an encrypted attribute called password
:
class Product < ApplicationRecord
encrypts :description # why you would do this I do not know!
end
Step 3: Encrypt and Decrypt Data
You can create a product with an encrypted description like this:
irb(main):001:0> product = Product.new(title: 'A title', description: 'a description', price: 25.1)
=> #<Product:0x000000010bae6b98 id: nil, title: "A title", price: 25.1, description: "a description", created_at: nil, updated_at: nil>
irb(main):002:0> product.save!
TRANSACTION (0.1ms) begin transaction
Product Create (1.7ms) INSERT INTO "products" ("title", "price", "description", "created_at", "updated_at") VALUES (?, ?, ?, ?, ?) [["title", "A title"], ["price", 25.1], ["description", "{\"p\":\"VeK6h5jQH9BS0KAgYQ==\",\"h\":{\"iv\":\"lLXAYRRDdGxDiR9o\",\"at\":\"Kwbu2PH9b//BoI/sVAPTVw==\"}}"], ["created_at", "2023-08-02 05:04:41.881694"], ["updated_at", "2023-08-02 05:04:41.881694"]]
TRANSACTION (1.4ms) commit transaction
=> true
When you save a product with an encrypted description, the description will be encrypted using the keys you added earlier.
When you load the record the decryption of the attribute is transparent.
irb(main):003:0> product = Product.last
Product Load (1.4ms) SELECT "products".* FROM "products" ORDER BY "products"."id" DESC LIMIT ? [["LIMIT", 1]]
=>
#<Product:0x000000010d53b610
...
irb(main):004:0> product.description
=> "a description"
Querying
What this does mean is that you cannot query the model.
irb(main):005:0> product = Product.where(description: 'a description')
Product Load (0.5ms) SELECT "products".* FROM "products" WHERE "products"."description" = ? [["description", "{\"p\":\"wdnix8h+WE2ffrk9Lg==\",\"h\":{\"iv\":\"t99H28mm/MzcsxuB\",\"at\":\"SHM+/SmpAVV/sBRBopKm9w==\"}}"]]
=> []
But, if you make the encrypted column deterministic
you can!
class Product < ApplicationRecord
validates :title, presence: true
encrypts :description
encrypts :title, deterministic: true # now encrypt the title deterministically
end
Now you can seamlessly query the model, just as if the column was not encrypted at all (note: this does probably decrease the security of things, but probably only marginally. Unless you work for a three letter organization it is probably fine).
irb(main):001:0> product = Product.new(title: 'Phil Title', description: 'phil description', price: 99.99)
=> #<Product:0x00000001050afae0 id: nil, title: "Phil Title", price: 99.99, description: "phil description", created_at: nil, updated_at: nil>
irb(main):002:0> product.save!
TRANSACTION (0.1ms) begin transaction
Product Create (1.2ms) INSERT INTO "products" ("title", "price", "description", "created_at", "updated_at") VALUES (?, ?, ?, ?, ?) [["title", "{\"p\":\"1RJHqWOus4fGDw==\",\"h\":{\"iv\":\"+/zLK97EHgeIUTg6\",\"at\":\"tzj449jTyTKIwnRXAQboQg==\"}}"], ["price", 99.99], ["description", "{\"p\":\"25jfOg0Z6uXc2gDxdW5O1Q==\",\"h\":{\"iv\":\"mfpYQ31+AdSpsRS9\",\"at\":\"ILCst1xDituOS7eiAQ5PyA==\"}}"], ["created_at", "2023-08-02 05:11:39.280148"], ["updated_at", "2023-08-02 05:11:39.280148"]]
TRANSACTION (0.9ms) commit transaction
=> true
irb(main):003:0> Product.where(title: 'Phil Title')
Product Load (0.2ms) SELECT "products".* FROM "products" WHERE "products"."title" = ? [["title", "{\"p\":\"1RJHqWOus4fGDw==\",\"h\":{\"iv\":\"+/zLK97EHgeIUTg6\",\"at\":\"tzj449jTyTKIwnRXAQboQg==\"}}"]]
=>
[#<Product:0x00000001062b3160
id: 2,
title: "Phil Title",
price: 99.99,
description: "phil description",
created_at: Wed, 02 Aug 2023 05:11:39.280148000 UTC +00:00,
updated_at: Wed, 02 Aug 2023 05:11:39.280148000 UTC +00:00>]
Additional Features
Encrypted attributes in Rails 7 also offer several other features, including:
- Support for ignoring case when querying encrypted data.
- Support for migrating unencrypted data to encrypted data.
- Especially cool because it lets you turn an unencrypted column into an encrypted one and migrate the data later.
- Support for multiple encryption schemes.
For a comprehensive guide, refer to the Rails Guides on Active Record Encryption.
Benefits of Using Encrypted Attributes
Utilizing encrypted attributes in Rails 7 offers a trifecta of compelling advantages. First and foremost, it provides Enhanced Security, enabling developers to protect sensitive data such as passwords and personal information from unauthorized access. This encryption ensures that even if data is breached, it remains unreadable without the proper decryption keys. Secondly, the feature boasts Ease of Use, with a simple implementation process that doesn't compromise usability. Developers can quickly encrypt attributes without needing extensive knowledge of cryptography. Lastly, encrypted attributes are Fully Integrated into the Rails Framework, ensuring seamless compatibility and support within the Rails ecosystem. This integration means that developers can leverage this powerful security feature without having to navigate the complexities of third-party solutions. Together, these benefits make encrypted attributes an attractive option for any Rails developer looking to enhance data security in their applications.
If you're developing an application that handles sensitive data, consider using encrypted attributes in Rails 7. It's a straightforward and effective way to bolster your security measures, ensuring that your data remains safe and secure.
You can find me on Twitter where I talk about Ruby on Rails, my company Zonmaster, and life in general. If you’re looking for help with your Rails project drop me a note on Twitter or LinkedIn
Top comments (0)