Navigation

Client-Side Encryption

New in MongoDB 4.2, client-side encryption allows administrators and developers to encrypt specific fields in MongoDB documents before inserting them into the database.

With client-side encryption, developers can encrypt fields client-side without any server-side configuration or directives. Client-side encryption supports workloads where applications must guarantee that unauthorized parties, including server administrators, cannot read the encrypted data.

Warning

Enabling Client Side Encryption reduces the maximum write batch size and may have a negative performance impact.

Installation

Client-side encryption requires the installation of additional packages.

libmongocrypt

Libmongocrypt is a C library used by the driver for client-side encryption. To use client-side encryption, you must install the libmongocrypt binary on the machine running your Ruby program.

To download a pre-built binary:

  • Download a tarball of all libmongocrypt variations here.
  • Extract the file you downloaded. You will see a list of directories, each corresponding to an operating system. Find the directory that matches your operating system and open it.
  • Inside that folder, open the folder called “nocrypto.” In either the lib or lb64 folder, you will find the libmongocrypt.so or libmongocrypt.dylib or libmongocrypt.dll file, depending on your OS.
  • Move that file to wherever you want to keep it on your machine. You may delete the other files included in the tarball.

To build the binary from source:

Once you have the libmongocrypt binary on your machine, specify the path to the binary using the LIBMONGOCRYPT_PATH environment variable. It is recommended that you add this variable to your rc files. For example:

export LIBMONGOCRYPT_PATH=/path/to/your/libmongocrypt.so

mongocryptd

Mongocryptd is a daemon that tells the driver which fields to encrypt in a given operation. It is only required for automatic encryption, which is an enterprise-only feature. If you only intend to use explicit encryption, you may skip this step.

Mongocryptd comes pre-packaged with enterprise builds of the MongoDB server (versions 4.2 and newer). For installation instructions, see the MongoDB manual.

In order to configure mongocryptd (for example, which port it listens on or the path used to spawn the daemon), it is necessary to pass different options to the Mongo::Client performing automatic encryption. See the :extra_options section of this tutorial for more information.

Automatic Encryption

Automatic encryption is a feature that allows users to configure a Mongo::Client instance to always encrypt specific document fields when performing database operations. Once the Mongo::Client is configured, it will automatically encrypt any field that requires encryption before writing it to the database, and it will automatically decrypt those fields when reading them.

Client-side encryption implements envelope encryption, which is the practice of encrypting data with a data key, which is in turn encrypted using a master key. Thus, using client-side encryption with MongoDB involves three main steps:

  1. Create a master key
  2. Create a data key (and encrypt it using the master key)
  3. Encrypt data using the data key

The example below demonstrates how to follow these steps with a local master key in order to perform automatic encryption.

Note

Automatic encryption is an enterprise only feature that only applies to operations on a collection. Automatic encryption is not supported for operations on a database or view, and operations that are not bypassed will result in error (see Auto Encryption Whitelist ). To bypass automatic encryption for all operations, set bypass_auto_encryption to true in auto_encryption_options.

Note

Automatic encryption requires the authenticated user to have the listCollections privilege action.

require 'mongo'

#####################################
# Step 1: Create a local master key #
#####################################

# A local master key is a 96-byte binary blob.
local_master_key = SecureRandom.random_bytes(96)
# => "\xB2\xBE\x8EN\xD4\x14\xC2\x13\xC3..."

#############################
# Step 2: Create a data key #
#############################

kms_providers = {
  local: {
    key: local_master_key
  }
}

# The key vault client is a Mongo::Client instance connected to the collection
# that will store your data keys.
key_vault_client = Mongo::Client.new(['localhost:27017'])

# Use an instance of Mongo::ClientEncryption to create a new data key
client_encryption = Mongo::ClientEncryption.new(
  key_vault_client,
  key_vault_namespace: 'admin.datakeys',
  kms_providers: kms_providers
)

data_key_id = client_encryption.create_data_key('local')
# => <BSON::Binary... type=ciphertext...>

#######################################################
# Step 3: Configure Mongo::Client for auto-encryption #
#######################################################

# Create a schema map, which tells the Mongo::Client which fields to encrypt
schema_map = {
  'encryption_db.encryption_coll': {
    properties: {
      encrypted_field: {
        encrypt: {
          keyId: [data_key_id],
          bsonType: "string",
          algorithm: "AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic"
        }
      }
    },
    bsonType: "object"
  }
}

# Configure the client for automatic encryption
client = Mongo::Client.new(
  ['localhost:27017'],
  auto_encryption_options: {
    key_vault_namespace: 'admin.datakeys',
    kms_providers: kms_providers,
    schema_map: schema_map
  }
)

collection = client.use('encryption_db')['encryption_coll']
collection.drop # Make sure there is no data in the collection

# The string "sensitive data" will be encrypted and stored in the database
# as ciphertext
collection.insert_one(encrypted_field: 'sensitive data')

# The data is decrypted before being returned to the user
collection.find(encrypted_field: 'sensitive data').first['encrypted_field']
# => "sensitive data"

# A client with no auto_encryption_options is unable to decrypt the data
client_no_encryption = Mongo::Client.new(['localhost:27017'])
client_no_encryption.use('encryption_db')['encryption_coll'].find.first['encrypted_field']
# => <BSON::Binary... type=ciphertext...>

The example above demonstrates using automatic encryption with a local master key. For more information about using the AWS Key Management Service to create a master key and create data keys, see the following sections of this tutorial:

Explicit Encryption

Explicit encryption is a feature that allows users to encrypt and decrypt individual pieces of data such as strings, integers, or symbols. Explicit encryption is a community feature and does not require an enterprise build of the MongoDB server to use. To perform all explicit encryption and decryption operations, use an instance of the ClientEncryption class.

Client-side encryption implements envelope encryption, which is the practice of encrypting data with a data key, which is in turn encrypted using a master key. Thus, using client-side encryption with MongoDB involves three main steps:

  1. Create a master key
  2. Create a data key (and encrypt it using the master key)
  3. Encrypt data using the data key

The example below demonstrates how to follow these steps with a local master key in order to perform explicit encryption.

require 'mongo'

#####################################
# Step 1: Create a local master key #
#####################################

# A local master key is a 96-byte binary blob.
local_master_key = SecureRandom.random_bytes(96)
# => "\xB2\xBE\x8EN\xD4\x14\xC2\x13\xC3..."

#############################
# Step 2: Create a data key #
#############################

kms_providers = {
  local: {
    key: local_master_key
  }
}

# The key vault client is a Mongo::Client instance connected to the collection
# that will store your data keys.
key_vault_client = Mongo::Client.new(['localhost:27017'])

# Use an instance of Mongo::ClientEncryption to create a new data key
client_encryption = Mongo::ClientEncryption.new(
  key_vault_client,
  key_vault_namespace: 'admin.datakeys',
  kms_providers: kms_providers
)

data_key_id = client_encryption.create_data_key('local')
# => <BSON::Binary... type=ciphertext...>

#####################################################
# Step 3: Encrypt a string with explicit encryption #
#####################################################

# The value to encrypt
value = 'sensitive data'

# Encrypt the value
encrypted_value = client_encryption.encrypt(
  'sensitive data',
  {
    key_id: data_key_id,
    algorithm: "AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic"
  }
)

# Create the client you will use to read and write the data to MongoDB
client = Mongo::Client.new(['localhost:27017'])
collection = client.use('encryption_db')['encryption_coll']
collection.drop # Make sure there is no data in the collection

# Insert the encrypted value into the collection
collection.insert_one(encrypted_field: encrypted_value)

# Use the client to read the encrypted value from the database, then
# use the ClientEncryption object to decrypt it
find_result = collection.find(encrypted_field: encrypted_value).first['encrypted_field']
# => <BSON::Binary...> (the find result is encrypted)

unencrypted_result = client_encryption.decrypt(find_result)
# => "sensitive data"

The example above demonstrates using explicit encryption with a local master key. For more information about using the AWS Key Management Service to create a master key and create data keys, see the following sections of this tutorial:

Creating a Master Key

Both automatic encryption and explicit encryption require an encryption master key. This master key is used to encrypt data keys, which are in turn used to encrypt user data. The master key can be generated in one of two ways: by creating a local key, or by creating a key in the Amazon Web Services Key Management Service (AWS KMS).

Local Master Key

A local master key is a 96-byte binary string. It should be persisted on your machine as an environment variable or in a text file.

Warning

Using a local master key is insecure and not recommended if you plan to use client-side encryption in production.

Run the following code to generate a local master key using Ruby:

local_master_key = SecureRandom.random_bytes(96)
# => "\xB2\xBE\x8EN\xD4\x14\xC2\x13\xC3..." (a binary blob)

AWS Master Key

It is recommended that you use Amazon’s Key Management Service to create and store your master key. To do so, follow steps 1 and 2 of the “Convert to a Remote Master Key” section in the MongoDB Client-Side Encryption documentation.

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

Creating a Data Key

Once you have created a master key, create a data key by calling the #create_data_key method on an instance of the Mongo::ClientEncryption class. This method generates a new data key and inserts it into the key vault collection, which is the MongoDB collection in which you choose to store your data keys. The #create_data_key method returns id of the newly-created data key in the form of a BSON::Binary object.

Create a Data Key Using a Local Master Key

If you have created a local master key, you may use it to generate a new data key with the following code snippet:

Warning

Using a local master key is insecure and not recommended if you plan to use client-side encryption in production.

# A Mongo::Client instance that will be used to connect to the key vault
# collection. Replace the server address with the address of the MongoDB
# server where you would like to store your key vault collection.
key_vault_client = Mongo::Client.new(['localhost:27017'])

client_encryption = Mongo::ClientEncryption.new(
  key_vault_client,
  # Replace with the database and collection names for your key vault collection
  key_vault_namespace: 'admin.datakeys',
  kms_providers: {
    local: {
      key: local_master_key
    }
  }
)

data_key_id = client_encryption.create_data_key('local')
# => <BSON::Binary... type=ciphertext...>

See the Local Master Key section for more information about generating a new local master key.

Create a Data Key Using an AWS Master Key

If you have created an AWS master key, note the access key ID and the secret access key of the IAM user that has permissions to use the key. Additionally, note the AWS region and the Amazon Resource Number (ARN) of your master key. You will use that information to generate a data key.

# A Mongo::Client instance that will be used to connect to the key vault
# collection. Replace the server address with the address of the MongoDB
# server where you would like to store your key vault collection.
key_vault_client = Mongo::Client.new(['localhost:27017'])

client_encryption = Mongo::ClientEncryption.new(
  key_vault_client,
  # Replace with the database and collection names for your key vault collection
  key_vault_namespace: 'admin.datakeys',
  kms_providers: {
    aws: {
      access_key_id: 'IAM-ACCESS-KEY-ID',
      secret_access_key: 'IAM-SECRET-ACCESS-KEY'
    }
  }
)

data_key_id = client_encryption.create_data_key(
  'aws',
  {
    master_key: {
      region: 'REGION-OF-YOUR-MASTER-KEY',
      key: 'ARN-OF-YOUR-MASTER-KEY'
    }

  }
)
# => <BSON::Binary... type=ciphertext...>

See the AWS Master Key section of this tutorial for more information about generating a new master key on AWS and finding the information you need to create data keys.

For more information about creating a data key, see the Create a Data Encryption Key section of the MongoDB manual.

Auto-Encryption Options

Automatic encryption can be configured on a Mongo::Client using the auto_encryption_options option Hash. This section provides an overview of the fields inside auto_encryption_options and explains how to choose their values.

:key_vault_client

The key vault client is a Mongo::Client instance that will be used to connect to the MongoDB collection containing your encryption data keys. For example, if your key vault was hosted on a MongoDB instance at localhost:30000:

key_vault_client = Mongo::Client.new(['localhost:30000'])

Mongo::Client.new(['localhost:27017],
  auto_encryption_options: {
    key_vault_client: key_vault_client,
    # ... (Fill in other options here)
  }
)

If your data keys are stored in the same MongoDB instance that stores your encrypted data, you may leave this option blank, and the top-level client will be used to insert and fetch data keys.

:key_vault_namespace

The key vault namespace is a String in the format "database_name.collection_name", where database_name and collection_name are the name of the database and collection in which you would like to store your data keys. For example, if your data keys are stored in the admin database in the datakeys collection:

Mongo::Client.new(['localhost:27017],
  auto_encryption_options: {
    key_vault_namespace: 'admin.datakeys',
    # ... (Fill in other options here)
  }
)

There is no default key vault namespace, and this option must be provided.

:schema_map

A schema map is a Hash with information about which fields to automatically encrypt and decrypt.

The code snippet at the top of this tutorial demonstrates creating a schema map using a Ruby Hash. While this will work, schema maps can grow quite large and it could be unweildy to include them in your Ruby code. Instead, it is recommended that you store them in a separate JSON (JavaScript Object Notation) file.

Before creating the JSON file, Base64-encode the UUID of the your data key.

Base64.encode64(data_key_id.data)
# => "sr6OTtQUwhPD..." (a base64-encoded string)

Then, create a new JSON file containing your schema map in the format defined by the JSON Schema Draft 4 standard syntax. You can read more about formatting your schema map in the Automatic Encryption Rules section of the MongoDB manual.

{
  "encryption_db.encryption_coll": {
    "properties": {
      "encrypted_field": {
        "encrypt": {
          "keyId": [{
            "$binary": {
              "base64": "YOUR-BASE64-ENCODED-DATA-KEY-ID",
              "subType": "04"
            }
          }],
          "bsonType": "string",
          "algorithm": "AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic"
        }
      }
    },
    "bsonType": "object"
  }
}

When you intend to use your schema map, convert it to a Ruby Hash using the BSON::ExtJSON module in the bson Ruby gem.

schema_map = BSON::ExtJSON.parse(File.read('/path/to/your/file.json'))
# => { 'encryption_db.encryption_coll' => { ... } }

Mongo::Client.new(['localhost:27017],
  auto_encryption_options: {
    schema_map: schema_map,
    # ... (Fill in other options here)
  }
)

Note

It is also possible to supply a schema map as a validator on a MongoDB collection. This is referred to as a “remote schema map,” while providing the schema map as an option on the Mongo::Client is called a “local schema map.”

Supplying a local schema map provides more security than relying on JSON schemas obtained from the server. It protects against a malicious server advertising a false JSON schema, which could trick the client into sending unencrypted data that should be encrypted.

See Server-Side Field Level Encryption Enforcement in the MongoDB manual for more information about using the schema map to create a JSON schema validator on your collection.

:bypass_auto_encryption

The :bypass_auto_encryption option is a Boolean that specifies whether the Mongo::Client should skip encryption when writing to the database. If :bypass_auto_encryption is true, the client will still perform automatic decryption of any previously-encrypted data.

Mongo::Client.new(['localhost:27017],
  auto_encryption_options: {
    bypass_auto_encryption: true,
    # ... (Fill in other options here)
  }
)

:extra_options

:extra_options is a Hash of options related to spawning mongocryptd. Every option in this Hash has a default value, so it is only necessary to provide the options whose defaults you want to override.

  • :mongocryptd_spawn_args - This is an Array<String> containing arguments for spawning mongocryptd. The Ruby driver will pass these arguments to mongocryptd on spawning the daemon. Possible arguments are:
    • "--idleShutdownTimeoutSecs" - The number of seconds mongocryptd must remain idle before it shuts itself down. The default value is 60.
    • "--port" - The port at which mongocryptd will listen for connections. The default is 27020.
  • :mongocryptd_uri - The URI that the driver will use to connect to mongocryptd. By default, this is "mongodb://localhost:27020".
  • :mongocryptd_spawn_path - The path to the mongocryptd executable. The default is "mongocryptd".
  • :mongocryptd_bypass_spawn - A Boolean indicating whether the driver should skip spawning mongocryptd.

For example, if you would like to run mongocryptd on port 30000, provide extra_options as follows:

  Mongo::Client.new(['localhost:27017],
  auto_encryption_options: {
    extra_options: {
      mongocryptd_spawn_args: ['--port=30000'],
      mongocryptd_uri: 'mongodb://localhost:30000',
    }
    # ... (Fill in other options here)
  }
)

Warning

The contents of :extra_options is subject to change in future versions of the client-side encryption API.

←   GridFS Query Cache  →