Docs Menu

Docs HomeMongoid

Automatic Client-Side Field Level Encryption

On this page

  • Installation
  • Install libmongocrypt
  • Install the automatic encryption shared library (Ruby driver 2.19+)
  • Install the mongocryptd (Ruby driver 2.18 or older)
  • Add ffi to your Gemfile
  • Create a Customer Master Key
  • Configure Clients
  • Create a Data Encryption Key
  • Configure Encryption Schema
  • Known Limitations
  • Working with Data
  • Encryption Key Management
  • Customer Master Keys
  • Data Encryption Keys
  • Encryption Keys Rotation
  • Adding Automatic Encryption To Existing Project

Since version 4.2 MongoDB supports Client-Side Field Level Encryption (CSFLE). This is a feature that enables you to encrypt data in your application before you send it over the network to MongoDB. With CSFLE enabled, no MongoDB product has access to your data in an unencrypted form.

You can set up CSFLE using the following mechanisms:

  • Automatic Encryption: Enables you to perform encrypted read and write operations without you having to write code to specify how to encrypt fields.

  • Explicit Encryption: Enables you to perform encrypted read and write operations through your MongoDB driver's encryption library. You must specify the logic for encryption with this library throughout your application.

Starting with version 9.0, Mongoid supports CSFLE's Automatic Encryption feature. This tutorial walks you through the process of setting up and using CSFLE in Mongoid.

Note

This tutorial does not cover all CSLFE features. You can find more information about MongoDB CSFLE in the server documentation.

Note

If you want to use explicit FLE, please follow the Ruby driver documentation.

You can find the detailed description of how to install the necessary dependencies in the driver documentation.

Note the version of the Ruby driver being used in your application and select the appropriate steps below.

This can be done one of two ways.

If you use the Ruby driver version 2.19 and above, the automatic encryption shared library should be installed by following the instructions on the Automatic Encryption Shared Library for Queryable Encryption page in the Server manual.

The steps required are as follows:

  1. Navigate to the MongoDB Download Center

  2. From the Version dropdown, select x.y.z (current) (the latest current version).

  3. In the Platform dropdown, select your platform.

  4. In the Package dropdown, select crypt_shared.

  5. Click Download.

Once extracted, ensure the full path to the library is configured within your mongoid.yml as follows:

development:
clients:
default:
options:
auto_encryption_options:
extra_options:
crypt_shared_lib_path: '/path/to/mongo_crypt_v1.so'

If you are using an older version of the Ruby driver mongocryptd will need to be installed manually. mongocryptd comes pre-packaged with enterprise builds of the MongoDB server (versions 4.2 and newer). For installation instructions, see the MongoDB manual.

The MongoDB Ruby driver uses the ffi gem to call functions from libmongocrypt. As this gem is not a dependency of the driver, it will need to be manually added to your Gemfile:

gem 'ffi'

A Customer Master Key (CMK) is used to encrypt Data Encryption Keys. The easiest way is to use a locally stored 96-bytes key. You can generate such a key using the following Ruby code:

require 'securerandom'
SecureRandom.hex(48) # => "f54ab...."

Later in this tutorial we assume that the Customer Master Key is available from the CUSTOMER_MASTER_KEY environment variable.

Warning

Using a local master key is insecure. It is recommended that you use a remote Key Management Service to create and store your master key. To do so, follow steps of the "Set up a Remote Master Key" in the MongoDB Client-Side Encryption documentation.

For more information about creating a master key, see the Create a Customer Master Key section of the MongoDB manual.

Automatic CSFLE requires some additional configuration for the MongoDB client. Assuming that your application has just one default client, you need to add the following to your mongoid.yml:

development:
clients:
default:
uri: mongodb+srv://user:pass@yourcluster.mongodb.net/blog_development?retryWrites=true&w=majority
options:
auto_encryption_options: # This key enables automatic encryption
key_vault_namespace: 'encryption.__keyVault' # Database and collection to store data keys
kms_providers: # Tells the driver where to obtain master keys
local: # We use the local key in this tutorial
key: "<%= ENV['CUSTOMER_MASTER_KEY'] %>" # Key that we generated earlier
extra_options:
crypt_shared_lib_path: '/path/to/mongo_crypt_v1.so' # Only if you use the library

A Data Encryption Key (DEK) is the key you use to encrypt the fields in your MongoDB documents. You store your Data Encryption Key in your Key Vault collection encrypted with your CMK.

To create a DEK in Mongoid you can use the db:mongoid:encryption:create_data_key Rake task:

% rake db:mongoid:encryption:create_data_key
Created data key with id: 'KImyywsTQWi1+cFYIHdtlA==' for kms provider: 'local' in key vault: 'encryption.__keyVault'.

You can create multiple DEKs, if necessary.

% rake db:mongoid:encryption:create_data_key
Created data key with id: 'Vxr5m+5cQISjDOruzZgE0w==' for kms provider: 'local' in key vault: 'encryption.__keyVault'.

You can also provide an alternate name for the DEK. This allows you to reference the DEK by name when configuring encryption for your fields. It also allows you to dynamically assign a DEK to a field at runtime.

% rake db:mongoid:encryption:create_data_key -- --key-alt-name=my_data_key
Created data key with id: 'yjF8hKmKQsqGeFGXlB9Sow==' with key alt name: 'my_data_key' for kms provider: 'local' in key vault: 'encryption.__keyVault'.

Now we can tell Mongoid what should be encrypted:

class Patient
include Mongoid::Document
include Mongoid::Timestamps
# Tells Mongoid what DEK should be used to encrypt fields of the document
# and its embedded documents.
encrypt_with key_id: 'KImyywsTQWi1+cFYIHdtlA=='
# This field is not encrypted.
field :category, type: String
# This field is encrypted using AEAD_AES_256_CBC_HMAC_SHA_512-Random
# algorithm.
field :passport_id, type: String, encrypt: {
deterministic: false
}
# This field is encrypted using AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic
# algorithm.
field :blood_type, type: String, encrypt: {
deterministic: true
}
# This field is encrypted using AEAD_AES_256_CBC_HMAC_SHA_512-Random
# algorithm and using a different data key.
field :ssn, type: Integer, encrypt: {
deterministic: false, key_id: 'Vxr5m+5cQISjDOruzZgE0w=='
}
embeds_one :insurance
end
class Insurance
include Mongoid::Document
include Mongoid::Timestamps
field :insurer, type: String
# This field is encrypted using AEAD_AES_256_CBC_HMAC_SHA_512-Random
# algorithm using the key which alternate name is stored in the
# policy_number_key field.
field :policy_number, type: Integer, encrypt: {
deterministic: false,
key_name_field: :policy_number_key
}
embedded_in :patient
end

Note

If you are developing a Rails application, it is recommended to set preload_models to true in mongoid.yml. This will ensure that Mongoid loads all models before the application starts, and the encryption schema is configured before any data is read or written.

  • MongoDB CSFLE has some limitations that are described on the CSFLE Limitations page in the Server manual. These limitations also apply to Mongoid.

  • Mongoid does not support encryption of embeds_many relations.

  • If you use :key_name_field option, the field must be encrypted using non-deterministic algorithm. To encrypt your field deterministically, you must specify :key_id option instead.

Automatic CSFLE usage is transparent in many situations.

Note

In code examples below we assume that there is a variable unencrypted_client that is a MongoClient connected to the same database but without encryption. We use this client to demonstrate what is actually persisted in the database.

Documents can be created as usual, fields will be encrypted and decrypted according to the configuration:

Patient.create!(
category: 'ER',
passport_id: '123456',
blood_type: 'AB+',
ssn: 98765,
insurance: Insurance.new(insurer: 'TK', policy_number: 123456, policy_number_key: 'my_data_key')
)
# Fields are encrypted in the database
unencrypted_client['patients'].find.first
# =>
# {"_id"=>BSON::ObjectId('6446a1d046ebfd701f9f4292'),
# "category"=>"ER",
# "passport_id"=><BSON::Binary:0x404080 type=ciphertext data=0x012889b2cb0b1341...>,
# "blood_type"=><BSON::Binary:0x404560 type=ciphertext data=0x022889b2cb0b1341...>,
# "ssn"=><BSON::Binary:0x405040 type=ciphertext data=0x012889b2cb0b1341...>,
# "insurance"=>{"_id"=>BSON::ObjectId('6446a1d046ebfd701f9f4293'), "insurer"=>"TK", "policy_number"=><BSON::Binary:0x405920 type=ciphertext data=0x012889b2cb0b1341...>}, "policy_number_key"=>"my_data_key"}

Fields encrypted using a deterministic algorithm can be queried. Only exact match queries are supported. For more details please consult the server documentation.

# We can find documents by deterministically encrypted fields.
Patient.where(blood_type: "AB+").to_a
# => [#<Patient _id: 6447e34d46ebfd3debdd9c39, category: "ER", passport_id: "123456", blood_type: "AB+", ssn: 98765>]

Your Customer Master Key is the key you use to encrypt your Data Encryption Keys. MongoDB automatically encrypts Data Encryption Keys using the specified CMK during Data Encryption Key creation.

The CMK is the most sensitive key in CSFLE. If your CMK is compromised, all of your encrypted data can be decrypted.

Important

Ensure you store your Customer Master Key (CMK) on a remote KMS.

To learn more about why you should use a remote KMS, see Reasons to Use a Remote KMS.

To view a list of all supported KMS providers, see the KMS Providers page.

MongoDB CSFLE supports the following Key Management System (KMS) providers:

Data Encryption Keys can be created using the db:mongoid:encryption:create_data_key Rake task. By default they are stored on the same cluster as the database. However, it might be a good idea to store the keys separately. This can be done by specifying a key vault client in mongoid.yml:

development:
clients:
key_vault:
uri: mongodb+srv://user:pass@anothercluster.mongodb.net/blog_development?retryWrites=true&w=majority
default:
uri: mongodb+srv://user:pass@yourcluster.mongodb.net/blog_development?retryWrites=true&w=majority
options:
auto_encryption_options:
key_vault_client: :key_vault # Client to connect to key vault
# ...

You can rotate encryption keys using the rewrap_many_data_key method of the Ruby driver. This method automatically decrypts multiple data encryption keys and re-encrypts them using a specified CMK. It then updates the rotated keys in the key vault collection. This method allows you to rotate encryption keys based on two optional arguments:

  • A filter used to specify which keys to rotate. If no data key matches the given filter, no keys will be rotated. Omit the filter to rotate all keys in your key vault collection.

  • An object that represents a new CMK. Omit this object to rotate the data keys using their current CMKs.

Here is an example of rotating keys using AWS KMS:

# Create a key vault client
key_vault_client = Mongo::Client.new('mongodb+srv://user:pass@yourcluster.mongodb.net')
# Or, if you declared the key value client in mongoid.yml, use it
key_vault_client = Mongoid.client(:key_vault)
# Create the encryption object
encryption = Mongo::ClientEncryption.new(
key_vault_client,
key_vault_namespace: 'encryption.__keyVault',
kms_providers: {
aws: {
"accessKeyId": "<IAM User Access Key ID>",
"secretAccessKey": "<IAM User Secret Access Key>"
}
}
)
encryption.rewrap_many_data_key(
{}, # We want to rewrap all keys
{
provider: 'aws',
master_key: {
region: 'us-east-2',
key: 'arn:aws:kms:us-east-2:...'
}
}
)

MongoDB automatic CSFLE supports encryption in place. You can enable encryption for your existing database, and will still able to read unencrypted data. All data written to the database will be encrypted. However, as soon as the encryption is enabled, all query operations will use encrypted data:

# We assume that there are two documents in the database, one created without
# encryption enabled, and one with encryption.
# We can still read both.
Patient.all.to_a
# =>
# [#<Patient _id: 644937ac46ebfd02468e58c8, category: "ER", passport_id: "DE-1257", blood_type: "AB+", ssn: 123456>,
# #<Patient _id: 644937c946ebfd029309b912, category: "ER", passport_id: "AT-1545", blood_type: "AB+", ssn: 987654>]
# But when we query, we can see only the latter one.
Patient.where(blood_type: 'AB+').to_a
# => [#<Patient _id: 644937c946ebfd029309b912, category: "ER", passport_id: "AT-1545", blood_type: "AB+", ssn: 987654>]

If you want to encrypt the existing database, it can be achieved by reading and writing back all data, even without any changes. If you decide to do so, please keep the following in mind:

  • Validate the integrity of existing data for consistent fidelity. CSFLE is type sensitive - for example you cannot store integers in a field that is declared as String.

  • For strings, make sure that empty values are always empty strings or just not set, but not nil (CSFLE doesn't support native null).

  • This operation requires application downtime.

←  Common ErrorsSchema Configuration →