Hybrid Cryptography on Rails


Hybrid cryptography allows certain servers to encrypt data without the ability to decrypt it. This can greatly limit damage in the event of a breach.

Suppose we have a service that sends text messages to customers. Customers enter their phone number through the website or mobile app.

With hybrid cryptography, we can set up web servers to only encrypt phone numbers. Text messages can be sent through background jobs which run on a different set of servers - ones that can decrypt and don’t allow inbound traffic. If internal employees need to view phone numbers, they can use a separate set of web servers that are only accessible through the company VPN.

  Encrypt Decrypt  
Customer web servers
Background workers No inbound traffic
Internal web servers Requires VPN


Install Libsodium and add Lockbox and RbNaCl to your Gemfile:

gem 'lockbox'
gem 'rbnacl'

Generate keys in the Rails console with:


Store the keys with your other secrets. This is typically Rails credentials or an environment variable (dotenv is great for this). Be sure to use different keys in development and production.


Only set the decryption key on servers that should be able to decrypt.

Database Fields

We’ll store phone numbers in an encrypted database field. Create a migration to add a new column for the encrypted data.

class AddEncryptedPhoneToUsers < ActiveRecord::Migration[5.2]
  def change
    add_column :users, :phone_ciphertext, :string

In the model, add:

class User < ApplicationRecord
  encrypts :phone, algorithm: "hybrid", encryption_key: ENV["PHONE_ENCRYPTION_KEY"], decryption_key: ENV["PHONE_DECRYPTION_KEY"]

Set a user’s phone number to ensure it works.


Suppose we also need to accept sensitive documents. We can take a similar approach with file uploads.

For Active Storage, use:

class User < ApplicationRecord
  encrypts_attached :document, algorithm: "hybrid", encryption_key: ENV["PHONE_ENCRYPTION_KEY"], decryption_key: ENV["PHONE_DECRYPTION_KEY"]

For CarrierWave, use:

class DocumentUploader < CarrierWave::Uploader::Base
  encrypt algorithm: "hybrid", encryption_key: ENV["PHONE_ENCRYPTION_KEY"], decryption_key: ENV["PHONE_DECRYPTION_KEY"]

You can also encrypt an IO stream directly.

box = Lockbox.new(algorithm: "hybrid", encryption_key: ENV["PHONE_ENCRYPTION_KEY"], decryption_key: ENV["PHONE_DECRYPTION_KEY"])


You’ve now seen an approach for keeping your data safe in the event a server is compromised. For more on data protection, check out Securing Sensitive Data in Rails.

Published February 28, 2019

Thanks to Luka Siemionov for the key image.

You might also enjoy

Client-Side Encryption with AWS and Ruby

Lockbox: Now with Types

Adding CSP to Rails

All code examples are public domain.
Use them however you’d like (licensed under CC0).