Active Record Encryption
Hello!
In this article, I will try to share my experience about working with the new feature in Rails: “Active Record Encryption”.
By the way, from now, I will try to writing (also think) in English when publishing an article in this blog because I will try to improve my English skill :)
I think in nowadays encryption will be the heart of the application, so I am really excited that Rails will has build-in encryption API (that probably will be released in the upcoming Rails 7).
I have tried several library about encryption before this, like lockbox, and attr_encrypted. With lockbox, I have a blog post about that: Enkripsi Data di Rails , and with attr_encrypted, I am using this in daily work job.
Lockbox is a new library that came in principle “easy to use”, you can see from their API, and also they has the build-in feature for migrating legacy data and rotating the keys. But for the attr_encrypted it doesn’t have built-in features like migration data, or rotating keys instead you should think by yourself, but because it’s elder and more popular from Lockbox, I think attr_encrypted is more stable.
But, in this post, I don’t want to explain the comparison of these packages, instead, I want to share about the new active record encryption.
We will talk about:
- How to implement it in the fresh table
- How to migrating the existing plain data to encrypted data
- How to rotate the keys
Installation
So, first, the installation. We will use the edge of the rails for this, so you can run with this command:
$ rails new try-active-record-encryption --master --database=mysql -T
We will use rails github master branch, since the active record encryption still not released yet when this article was written.
Add the Active Record Encryption
After we successully generate the rails application, we will run this command to create the needed keys:
$ bin/rails db:encryption:init
primary_key: 9f8e08d77d121e282d5e6813937fc430
deterministic_key: gReNA2ZZgi8z5OKLBqqMYPpTVYalhQMp
key_derivation_salt: q6xHC9V0L1lKd1OPng5Y6MFKoIbEEWYM
You can add this to your rails credentials, by running this command:
$ EDITOR=nano rails credentials:edit
And paste the active_record_encryption
key to that file. Note: you can change the editor (nano) to your favorite terminal editors like vi, vim, or emacs.
After you successfully added the encryption keys, you can implement the encryption in your model.
Let’s create a new model:
$ rails g model Article title:text body:text
I prefer to use text
field when storing encrypted data since the encrypted data will be overload (it means the size will be bigger rather than plain data).
So, in the article model, you can implement encrypt like this:
# app/models/article.rb
class Article < ApplicationRecord
encrypts :title
end
Yups thats it!
And you can try to add new record by console like this:
$ bin/rails c
Running via Spring preloader in process 51510
Loading development environment (Rails 7.0.0.alpha)
irb(main):001:0> Article.create title: "Hello, world", body: "sample body"
TRANSACTION (0.3ms) BEGIN
Article Create (0.4ms) INSERT INTO `articles` (`title`, `body`, `created_at`, `updated_at`) VALUES ('{\"p\":\"LalKiAR0STeNu7QL\",\"h\":{\"iv\":\"NkV+r1FYTZWxBtjJ\",\"at\":\"asWpEcxw7ppd/7zUTDaCPw==\"}}', 'sample body', '2021-06-20 11:15:40.884861', '2021-06-20 11:15:40.884861')
TRANSACTION (0.7ms) COMMIT
=>
#<Article:0x00007fd3ccc19380
id: 1,
title: "Hello, world",
body: "sample body",
created_at: Sun, 20 Jun 2021 11:15:40.884861000 UTC +00:00,
updated_at: Sun, 20 Jun 2021 11:15:40.884861000 UTC +00:00>
Then it’s works! in database we don’t store the plain “Hello, world”, instead we will store a json string like this:
{\"p\":\"ll21rWpxN7Trx9ww\",\"h\":{\"iv\":\"832T/EwP/Cn4Z9ny\",\"at\":\"SlMJtHa+LOWXusPEYfxr8g==\"}}
So, we can tell this is the encrypted version of ‘Hello, world’.
Migrating legacy plain data to encrypted data
In this section we will try to encrypt the legacy plain data, in this case that is the body field. In the previous section we already add new record, so now in database we already store plain text which is “sample body”.
The first thing to do when want to migrating the plain data to encrypted one is to add this config:
# config/application.rb
config.active_record.encryption.support_unencrypted_data = true
This config to add ability to reading plain data in encypted attributes. So, when can make sure there is no downtime when migration the data.
The second thing is modify the model, to add the encrypts
callback to body attribute:
# app/models/article.rb
class Article < ApplicationRecord
encrypts :title
encrypts :body
end
After that, you can run this command, to migrating the deta
$ bin/rails c
irb(main):003:1* Article.all.each do |article|
irb(main):004:1* article.encrypt
irb(main):005:0> end
Article Load (0.2ms) SELECT `articles`.* FROM `articles`
TRANSACTION (0.2ms) BEGIN
Article Update (8.4ms) UPDATE `articles` SET `articles`.`title` = '{\"p\":\"CboWd5E1pnNWRSiw\",\"h\":{\"iv\":\"V3t5qpnkzHyytRrc\",\"at\":\"MGp/guZeSWyZbYl7Mo0k2Q==\"}}', `articles`.`body` = '{\"p\":\"DtarlSr//ROYII0=\",\"h\":{\"iv\":\"zQvZCEnNP8baDFTn\",\"at\":\"QeMfimZ9Gb5G2rMb5tLZXw==\"}}' WHERE `articles`.`id` = 1
TRANSACTION (1.1ms) COMMIT
=>
[#<Article:0x00007f8af745c5c0
id: 1,
title: "Hello, world",
body: "sample body",
created_at: Sun, 20 Jun 2021 11:15:40.884861000 UTC +00:00,
updated_at: Sun, 20 Jun 2021 11:15:40.884861000 UTC +00:00>]
So, now the body field is encypted.
{\"p\":\"DtarlSr//ROYII0=\",\"h\":{\"iv\":\"zQvZCEnNP8baDFTn\",\"at\":\"QeMfimZ9Gb5G2rMb5tLZXw==\"}}
Rotating the keys
To make sure the app is secure, it’s good to keep rotating the secret keys in every spesific time. So, in this section I will share to how rotating keys in active record encryption.
Active record encryption supports multiple secret keys, so we can add new secret key by adding the key like this:
active_record_encryption:
primary_key:
- 9f8e08d77d121e282d5e6813937fc430
- 121bd2026c924a6e0407b21a79c5a8a8 # the new keys (active)
deterministic_key: gReNA2ZZgi8z5OKLBqqMYPpTVYalhQMp
key_derivation_salt: q6xHC9V0L1lKd1OPng5Y6MFKoIbEEWYM
So, when we add new keys like this, the new record will use the actived key 121bd2026c924a6e0407b21a79c5a8a8
. And for the old records we still can read since we still have the keys in the credentials file 9f8e08d77d121e282d5e6813937fc430
.
To make all data use the newest secret key, you can do like this:
Article.all.each do |article|
article.encrypt
end
And after that, you can safe to delete the old key:
active_record_encryption:
primary_key: 121bd2026c924a6e0407b21a79c5a8a8
deterministic_key: gReNA2ZZgi8z5OKLBqqMYPpTVYalhQMp
key_derivation_salt: q6xHC9V0L1lKd1OPng5Y6MFKoIbEEWYM
For rotating keys there is some error in rails guides right now, if you see in this, the active keys pointing to the first key, but after i tried in local, the active key will pointing to the last key, I try to fix this guides, if you are interested you can check this PR.
I think that’s all, thanks for reading and happy hacking!