CAVEAT: Always refer to established best practice when doing anything related to security, and use well-established encryption libraries/software - don't try to roll your own!


Fortunately, Rails has a useful helper class for just this purpose: ActiveSupport::MessageEncryptor (backed by Ruby's OpenSSL bindings), which takes a key and uses it to encrypt/decrypt a string. So that you can avoid using the same key every time you encrypt something, Rails also has a ActiveSupport::KeyGenerator, which can generate keys based on a base secret and a salt.

We can thus write a little class that wraps all this up:

class Encryptor
  def initialize(key, salt)
    passphrase =
    @encryptor =

  def encrypt(plaintext)

  def decrypt(encrypted_data)

Since ActiveSupport::MessageEncryptor uses a random initialisation vector, the encrypted data produced will be different every time - this is something to keep in mind when testing your code - but will always decrypt to the same value:

encryptor ="my_secret_key", "my_secret_salt")

first_value = encryptor.encrypt("secret message") #=> "TnQvV2p0MTlTWXA2SzZ3Rk5IVi8wQjVtcldwMFZJZ0pTSHRIZ2J6bkZaST0tLVkxZVhuR2dERXo0eDU1clBBcTBXZFE9PQ==--7f56992cc41378ec8df2c74342b9ef1b68a40673"
second_value = encryptor.encrypt("secret message") #=> "ZFRnMVhYT3IxVW84MTFxQ0t4NEd6bW5GdUpxQXN2Q1ZvT3pCL3hvN2o0ND0tLXhXenlpbS85R2JnSVExSEEyREN0WWc9PQ==--9a2ef4de0b0afa04bbbe370b7c887efa4fa9873b"

encryptor.decrypt(first_value)  #=> "secret message"
encryptor.decrypt(second_value) #=> "secret message"

Key/salt generation

Ideally, you would get the key for the encryption from the user - this is generally how two-factor authentication works - but since that's unfeasible if we want to access APIs without the user's permission every time, we need to store the key somewhere. That somewhere needs to different from where the encrypted values are stored, meaning that an attacker would have to compromise two sources to get all the information needed (in this case, the database plus whenever the secret is stored).

Storing production keys in your codebase (Git repo) is a big no-no, so most people go for storing keys in the Unix environment. In our case, we wanted a variety of keys and salts, so that not every API key was encrypted with the same key/salt, giving us no. of keys * no. of salts combinations to encrypt with.

As an example, here's a little method for generating keys/salts (in this case, we create 10 different values):

def generate_random_values
  10.times.each_with_object({}) do |number, acc|
    index = sprintf "%02d", number

    acc[index] = SecureRandom.hex(64)

This method can then be used to generate a local YAML file for your development/test environments, or for ENV variables in production.


As a one-time task, you'll need to write out a YAML file for your keys/salts:

["keys", "salts"].each do |filename|
  my_secrets = {
    development: generate_random_values,
    test:        generate_random_values

  File.write Rails.root.join("config", "secret_#{filename}.yml"), my_secrets.to_yaml

We follow the database.yml format of having a YAML file "namespaced" by environment:

  00: 022b5940ca52e6d45e7f...
  01: 8f2938d4d3ac844e1c3a...
  # (snip)

  00: 273681232d8fe0c9ec9f...
  01: ee01cc498888607c59c1...
  # (snip)

This format means we can use a neat Rails method for loading these values into our app:

# config/initializers/load_secret_stuff.rb
SECRET_KEYS  = Rails.application.config_for("secret_keys")
SECRET_SALTS = Rails.application.config_for("secret_salts")

The #config_for method takes the name of a YAML file in your config directory, runs it through ERB, then returns the values under the environment key. This method was only introduced in Rails 4.2.0, but the functionality isn't too hard to reproduce for earlier versions.


For production environments, we want to retrieve the keys/salts from the ENV, so we need to modify our secret_{keys,salts}.yml files to use good ol' ERB interpolation:

# secret_{keys,salts}.yml
# Append this to the bottom:
  00: <%= ENV["MY_SECRET_{KEY, SALT}_00"] %>
  01: <%= ENV["MY_SECRET_{KEY, SALT}_01"] %>
  # ...and so on

To save typing this out by hand, here's a quick script to generate the necessary YAML:

encryption_type = "KEY" # or "SALT"

puts 10.times.each_with_object({}) do |number, acc|
  index = sprintf "%02d", number

  acc[index] = %Q{<%= ENV["MY_SECRET_#{encryption_type}_#{index}\"] %>}

Now that we've got a way to load the keys/salts from the environment, we need to generate them and add them to production. We're using Heroku, so we can use their API to upload the keys/salts to our app:

require "platform-api" # gem for the Heroku API

task :send_secrets_to_heroku do
  # See for instructions on how to
  # obtain an OAuth key from Heroku
  heroku = PlatformAPI.connect_oauth("your_heroku_oauth_key_here") # don't commit this key!

  # Keys
  generate_random_values.each_with_index do |key, index|
    puts "Uploading key #{index}: #{key}"
    heroku.config_var.update("name_of_your_app_on_heroku", "MY_SECRET_KEY_#{index}" => key)

  # Salts
  generate_random_values.each_with_index do |salt, index|
    puts "Uploading salt #{index}: #{salt}"
    heroku.config_var.update("name_of_your_app_on_heroku", "MY_SECRET_SALT_#{index}" => salt)

Bear in mind that Heroku limits you to 16KB of ENV data, so you can't go crazy and store thousands of key/salt pairs. The idea is to have enough that you have a reasonable number of combinations to choose from (for example, 10 salts and 10 keys gives you 100 different combinations). How you choose which key/salt to use will depend on your app, but you want something that always gives you the same key/salt pair. Something like the created_at timestamp of a model (which doesn't change), would be a good choice.

You probably want to keep a copy of these keys in a very safe place in case Heroku goes down spectacularly and leaves you unable to decrypt any information, such as an encrypted volume not available to the internet, a bank vault, down an abandoned mineshaft etc.


There's no such thing as perfect security, but at least separating the storage of your keys/secrets from your encrypted data gives you a little more piece of mind that an SQL injection attack won't walk off with all your customers' sensitive data.

Security is also a moving target, and so we're always looking for ways to make our app more secure. We make sure we make full use of Rails' SQL-escaping for all queries, and audit Digestive on every push and pull request with CodeClimate to help us catch any obvious security holes we may have missed.

If you're interested in finding out more about Digestive, head over to

Image by Rama on Wikimedia Commons