Simple Two Way Encryption for Ruby on Rails

There are many options to choose from when you need to store an encrypted value in a database. After trying a few, I recently settled on Sean Huber’s delightful attr_encrypted gem.

To use attr_encrypted you’ll need to first install the gem (gem install shuber-attr_encrypted --source http://gems.github.com), then either require it in your class or via config.gem. I’ll demonstrate how to use attr_encrypted with a User record that, for whatever reason, needs to store the users Social Security Number (SSN).

Starting from scratch, let’s generate a User model with columns for the users name and encrypted SSN:

script/generate model User name:string encrypted_ssn:string.

You probably noticed that I created the column encrypted_ssn rather than ssn. This is because attr_encrypted expects the attribute you want to encrypt to be titled encrypted_#{attribute}. I’ll show you how to manipulate this in a moment.

Let’s encrypt the users SSN by calling attr_encrypted in our User model:

class User < ActiveRecord::Base
require 'attr_encrypted' #if you aren't using config.gem
attr_accessible :name, :ssn
attr_encrypted :ssn, :key => "some_key_you_like"

Be sure to declare :ssn in both your mass-assignment allowance and your encrypted attributes. Otherwise, you will get the famous “can’t mass assign…” warning in your log, or if you are validating presence of any encrypted attributes you will receive an error that they are blank.

From here, feel free to proceed as normal. Save the users SSN to the database from a form using the :ssn method, not the :encrypted_ssn method (text_field_tag 'ssn'). This will save the encrypted SSN to your db. Calling @user.ssn will yield the users decrypted SSN.

A Couple Extras

Pass the :attribute option to attr_encrypted if you would like the column to be something other than encrypted_#{attribute}.

attr_encrypted :ssn, :key => "some_key", :attribute => "private_ssn"

You will probably need to make sure that the SSN is unique, yes? In this case you want to validate the encrypted value:

validates_uniqueness_of :encrypted_ssn

As long as you are encrypting every SSN record with the same :key this will validate properly. Cover your bases and add a unique index on encrypted_ssn in your database, also.

attr_encrypted offers quite a bit more functionality, such as custom encryptors, custom algorithms, default options and marshaling. Be sure to read through the documentation at github.com/shuber/attr_encrypted.

Finally, thanks to Sean for helping me clear up a few things in my own use. He’s quite accessible.

Tags: , , ,

Leave your two cents...







(I would never publish this)

3 people shared their two cents about “Simple Two Way Encryption for Ruby on Rails”

  1. sco Says:

    Nice article. I think it’s worth mentioning, though, that the encrypted data is only as safe as the key. So if an attacker just got access to the database, the SSNs are safe. But if they got access to the app server (or your git repo, or whatever), then the secrets would be out.

  2. Eric Hurst Says:

    Sco,

    You are quite right. Sean addresses this challenge here: http://wiki.github.com/shuber/attr_encrypted/questionsandanswers. Basically, set the key as a proc, and require the user to enter a password anytime they wish to access any encrypted data.

  3. sco Says:

    Very cool, nice that the gem supports that.

    Another solution (sort of) is to use asymmetric (public key) encryption — so that one key is used to encrypt stuff, but another key is require to decrypt it. E.g., if you’re storing credit card numbers, you could set up a separate billing server with the private key, and keep it behind a very tight firewall. That way, you could give your attacker access to the whole database and application without revealing the secrets. Here’s an old-but-good writeup on that approach: http://blog.leetsoft.com/2006/03/14/simple-encryption