Simple encryption of ActiveRecord fields

For past week, I have been working on encryption solution for a Rails app. The requirement was to encrypt chosen fields like ssn of an ActiveRecord model.

I’ve research a variety of solutions, including attr_encrypted and encryptor gems.

I want to show a simple way of encryption that combines ActiveRecord::Base.serialize and OpenSSL::Cipher, which comes with the Ruby stdlib.

Few things to bear in mind:

  • this kind of encryption helps only in case when your database is stolen
  • if hacker gets access to Rails console or ENV['ENCRYPTION_KEY'], you’re hacked
  • you may want to use IV and salt for sensitive data
  • by using Marshal, our encrypted field can store instance of any class (Date, Time, whatever!)
# lib/crypt.rb
module Crypt
class << self
def encrypt(value)
crypt(:encrypt, value)
end

def decrypt(value)
crypt(:decrypt, value)
end

def encryption_key
ENV.fetch('ENCRYPTION_KEY')
end

ALGO = 'aes-256-cbc'.freeze
def crypt(cipher_method, value)
cipher = OpenSSL::Cipher::Cipher.new(ALGO)
cipher.send(cipher_method)
cipher.pkcs5_keyivgen(encryption_key)
result = cipher.update(value)
result << cipher.final
end
end
end

# lib/encrypted_coder.rb
# custom coder for Rails serialized attribute
# more examples: https://github.com/rails/rails/tree/4-2-stable/activerecord/lib/active_record/coders
# encrypted value has to be stored as base64 because it's not UTF-safe
class EncryptedCoder
def load(value)
return if value.nil?

Marshal.load(
Crypt.decrypt(
Base64.decode64(value)))
end

def dump(value)
Base64.encode64(
Crypt.encrypt(
Marshal.dump(value)))
end
end

# app/models/wow_such_secure_model.rb
class WowSuchSecureModel < ActiveRecord::Base
serialize :ssn, EncryptedCoder.new
end

Done! You can use EncryptedCoder in any model.

A quick demo:

pry(main)> model = WowSuchSecureModel.create(ssn: "11-22-333")
(0.2ms) BEGIN
SQL (13.2ms) INSERT INTO "table" ("ssn", "created_at", "updated_at")
VALUES ($1, $2, $3) RETURNING "id"
[["ssn", "S9CTpTxsuG1mFExrFzyy1XD1qtxpiTKGOiopvFhuuwY=\n"], ["created_at", "2015-12-18 21:52:24.425346"], ["updated_at", "2015-12-18 21:52:24.425346"]]
(7.5ms) COMMIT
=> #<WowSuchSecureModel:0x007f803a19f4f8
id: 4,
ssn: "11-22-333",
created_at: Fri, 18 Dec 2015 21:52:24 UTC +00:00,
updated_at: Fri, 18 Dec 2015 21:52:24 UTC +00:00>
pry(main)> model.ssn
=> "11-22-333"
pry(main)> WowSuchSecureModel.last.ssn
=> "11-22-333"
pry(main)> WowSuchSecureModel.last.ssn_before_type_cast
=> "S9CTpTxsuG1mFExrFzyy1XD1qtxpiTKGOiopvFhuuwY=\n"

Comments

comments powered by Disqus