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
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
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.
# 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.