Sensitive Data in MongoDB

At KoanHealth, we work with healthcare data and use Ruby and MongoDB as our primary database to store patient data. This means that is has to be secure at rest and over the network. We looked at a few gems for storing encrypted data, but they:

  • Weren’t specific to MongoDB, and felt clunky
  • Made transitioning between raw and encrypted data difficult

Existing solutions

There are some solutions, but we really just needed a gem that:

  • Encrypts data at a field level
  • Makes it easy to access the raw and encrypted value
  • Allows developers to use their existing encryption gem

Encrypted fields

So we’ve built mongoid-encrypted-fields. We use Mongoid which allows custom field types, making it easy to extend. We’ve created “Encrypted” types that align with the base types already supported: String, Date/Time/DateTime, and Hash. From a developer standpoint, the fact that the data is encrypted is very transparent.

Install

gem 'mongoid-encrypted-fields'

Usage

Configure the cipher - we use GibberishCipher and it can be found in examples which uses the Gibberish gem, but we only need something with 2 methods: encrypt and decrypt

# Gibberish uses a unique salt for every encryption, but we need the same text to return the same ciphertext
# so Searching for encrypted field will work
class GibberishCipher
  def initialize(password, salt)
    cipher = Gibberish::AES.new(password)
    @salt = salt
  end

  def encrypt(data)
    @cipher.encrypt(data, salt: @salt)
  end

  def decrypt(data)
    @cipher.decrypt(data)
  end
end

Mongoid::EncryptedFields.cipher = GibberishCipher.new(ENV['MY_PASSWORD'], ENV['MY_SALT']) 

Encrypt the fields you choose:

class Person
  include Mongoid::Document

  field :name, type: String
  field :ssn, type: Mongoid::EncryptedString
end

The field returns the raw value:

person = Person.new(ssn: '123456789')
person.ssn # => '123456789'

The encrypted value is accessible with the “encrypted” method or using the hash syntax:

person.ssn.encrypted # => <encrypted string>

# It can also be accessed using the hash syntax supported by Mongoid
person[:ssn] # => <encrypted string>

The encrypted value is stored in the model’s attributes - which means only the encrypted data is passed across the network and stored in the database.

Finding a model with an encrypted field works automatically (equality only):

Person.where(ssn: '123456789').count() # ssn is encrypted before querying the database

The gem has been working well for us as we roll our first products into production. Feel free to check out the source on Github.

comments powered by Disqus