CipherStash
CipherStash Documentation

Encrypt your first database column

In the previous steps, we have:

  • Created a Rails app.
  • Installed ActiveStash.
  • Created a User model.
  • Used ActiveStash Assess to identify where sensitive data lives in our database.

In this step we will:

  • Add application-level encryption — using ActiveRecord Encryption — to the User model.
  • Add RSpec to our application.
  • Use the ActiveStash Assess RSpec matcher to keep track of what fields you need to encrypt, as you incrementally roll out Application Level Encryption in your app.

Application-level encryption encrypts our sensitive user data before sending it to the database, using keys only our Rails application can access.

1. Create active_record encryption keys:

$ rails db:encryption:init

This will output a YAML fragment that looks like this:

active_record_encryption:
  primary_key: *****************
  deterministic_key: *****************
  key_derivation_salt: *****************

You will see key material instead of the ***** above.

Make sure you record this for use in the next step.

2. Add these keys to your Rails credentials:

Using Vim as the editor:

$ EDITOR='vim' rails credentials:edit

Using VSCode as the editor:

$ EDITOR='code --wait' rails credentials:edit

This command will create the credentials file (at config/credentials.yml.enc) if it does not exist.

Copy and paste the ActiveRecord Encryption keys from the previous step into this file.

Save and close the file.

You should see the below confirmation message:

File encrypted and saved.

3. Install RSpec:

  • Add RSpec to your Gemfile:
gem 'rspec-rails'
  • Install:
$ bundle install
  • Initialise:
$ rails g rspec:install

4. Set up the ActiveStash Assess RSpec matcher to verify what fields have been encrypted:

First, make sure that the matcher is required in spec/rails_helper.rb:

# near the top of rails_helper.rb
require 'active_stash/matchers'

Next, add the matcher to the spec for the model that you’d like to test. The following example verifies that all fields reported as sensitive for the User model are encrypted:

# spec/models/user_spec.rb
require "rails_helper"

RSpec.describe User do
  it "encrypts sensitive fields", pending: "unenforced" do
    expect(described_class).to encrypt_sensitive_fields
  end
end

This helps you keep track of what fields you need to encrypt, as you incrementally roll out Application Level Encryption on your app.

As the example above shows, we recommend you start out by marking the test as pending. This will stop the test from failing while you incrementally encrypt database fields. Once you have encrypted all the fields identified by ActiveStash Assess, remove the pending so your tests will fail if the database field becomes unencrypted.

For the purposes of this tutorial, unmark the test as pending so we can see the failure.

# spec/models/user_spec.rb
require "rails_helper"

RSpec.describe User do
  it "encrypts sensitive fields" do
    expect(described_class).to encrypt_sensitive_fields
  end
end

Run:

rspec ./spec

As we haven’t encrypted any fields, our test fails:

F

Failures:

  1) User encrypts sensitive fields
     Failure/Error: expect(described_class).to encrypt_sensitive_fields
       Unprotected sensitive fields: name, email, suburb
     # ./spec/models/user_spec.rb:5:in `block (2 levels) in <top (required)>'

Finished in 0.0624 seconds (files took 1.46 seconds to load)
1 example, 1 failure

Failed examples:

rspec ./spec/models/user_spec.rb:4 # User encrypts sensitive fields

5. Declare the attributes that we want to encrypt in our user model:

# app/models/user.rb
class User < ApplicationRecord
  encrypts :name, :email, :suburb
end

ActiveRecord Encryption will:

  1. Encrypt these attributes before saving them to the database, and
  2. Decrypt these attributes when retrieving from the database

We now have application-level encryption setup in our Rails application.

6. Run the User model test again:

Now that we have encrypted the sensitive fields, our test will pass.

Run:

rspec ./spec
.

Finished in 0.0555 seconds (files took 1.1 seconds to load)
1 example, 0 failures

7. Create a new user:

Start a Rails console:

$ rails c

Create a new user:

User.create(name: "Grace Hopper", email: "grace@example.com", suburb: "Sydney")

The executed SQL looks like this:

irb(main):001:0> User.create(name: "Grace Hopper", email: "grace@example.com", suburb: "Sydney")
   (0.7ms)  SELECT sqlite_version(*)
  TRANSACTION (0.0ms)  begin transaction
  User Create (0.7ms)  INSERT INTO "users" ("name", "email", "suburb", "created_at", "updated_at") VALUES (?, ?, ?, ?, ?)  [["name", "{\"p\":\"hc5yYC9+B3TuOOrL\",\"h\":{\"iv\":\"9FZpub7+lgss8+Ki\",\"at\":\"RV01+UyMrJ3cue3V1ZPjiw==\"}}"], ["email", "{\"p\":\"6oAjN5bALqJZYrYxJpabWUU=\",\"h\":{\"iv\":\"Js4FyAedVBC3wbp6\",\"at\":\"Mtpc051PWaqCyuS4cR+IIw==\"}}"], ["suburb", "{\"p\":\"FuVHAkmY\",\"h\":{\"iv\":\"8MKwiDjqyGm/iUEN\",\"at\":\"QAt7am5l9J5GLalx8Dyz3w==\"}}"], ["created_at", "2022-08-24 22:20:39.975424"], ["updated_at", "2022-08-24 22:20:39.975424"]]
  TRANSACTION (0.6ms)  commit transaction

We can see that the data is encrypted before it is sent to the database.

But now, when we try to query the record we inserted, we get no results.

irb(main):004:0> User.where(name: "Grace Hopper")
  User Load (2.2ms)  SELECT "users".* FROM "users" WHERE "users"."name" = ?  [["name", "{\"p\":\"9lU1EJmJhAloDyle\",\"h\":{\"iv\":\"qyHsO+dc/NTAtY0m\",\"at\":\"HgDMa+lPfU4nphnzEz2r+Q==\"}}"]]
=> []

By default ActiveRecord uses non-deterministic encryption. This means encrypting the same data twice will result in different ciphertexts.

In the executed SQL for the query, we can see the ciphertext generated from the name is different to what was stored in the database.

This is why we get no results.

This means we can only use the record’s ID to query the record.

irb(main):003:0> User.where(id: 1)
  User Load (0.3ms)  SELECT "users".* FROM "users" WHERE "users"."id" = ?  [["id", 1]]
=>
[#<User:0x000000013f197b58
  id: 1,
  name: "Grace Hopper",
  email: "grace@example.com",
  suburb: "Sydney",
  created_at: Wed, 24 Aug 2022 04:29:21.648359000 UTC +00:00,
  updated_at: Wed, 24 Aug 2022 04:29:21.648359000 UTC +00:00>]

Exit out of the Rails console by typing exit or pressing Ctrl + D.

In our next step we’ll learn how ActiveStash gives us back our querying capabilities on encrypted data.