Using Client-Side Field Level Encryption

New in MongoDB 4.2, Client-Side Field Level Encryption (also referred to as Client-Side Encryption) allows administrators and developers to encrypt specific data fields in addition to other MongoDB encryption features.

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.

Automatic encryption, where sensitive fields in commands are encrypted automatically, requires an Enterprise-only process to do query analysis.

Installation

libmongocrypt

There is a separate library, libmongocrypt, that must be installed prior to configuring libmongoc to enable Client-Side Encryption.

libmongocrypt depends on libbson. To build libmongoc with Client-Side Encryption support you must:

  1. Install libbson

  2. Build and install libmongocrypt

  3. Build libmongoc

To install libbson, follow the instructions to install with a package manager: Install libbson with a Package Manager or build from source with cmake (disable building libmongoc with -DENABLE_MONGOC=OFF):

$ cd mongo-c-driver
$ mkdir cmake-build && cd cmake-build
$ cmake -DENABLE_AUTOMATIC_INIT_AND_CLEANUP=OFF -DENABLE_MONGOC=OFF ..
$ cmake --build . --target install

To build and install libmongocrypt, clone the repository and configure as follows:

$ cd libmongocrypt
$ mkdir cmake-build && cd cmake-build
$ cmake -DENABLE_SHARED_BSON=ON ..
$ cmake --build . --target install

Then, you should be able to build libmongoc with Client-Side Encryption.

$ cd mongo-c-driver
$ mkdir cmake-build && cd cmake-build
$ cmake -DENABLE_AUTOMATIC_INIT_AND_CLEANUP=OFF -DENABLE_MONGOC=ON -DENABLE_CLIENT_SIDE_ENCRYPTION=ON ..
$ cmake --build . --target install

mongocryptd

The mongocryptd binary is required for automatic Client-Side Encryption and is included as a component in the MongoDB Enterprise Server package. For detailed installation instructions see the MongoDB documentation on mongocryptd.

mongocryptd performs the following:

  • Parses the automatic encryption rules specified to the database connection. If the JSON schema contains invalid automatic encryption syntax or any document validation syntax, mongocryptd returns an error.

  • Uses the specified automatic encryption rules to mark fields in read and write operations for encryption.

  • Rejects read/write operations that may return unexpected or incorrect results when applied to an encrypted field. For supported and unsupported operations, see Read/Write Support with Automatic Field Level Encryption.

A mongoc_client_t configured with auto encryption will automatically spawn the mongocryptd process from the application’s PATH. Applications can control the spawning behavior as part of the automatic encryption options. For example, to set a custom path to the mongocryptd process, set the mongocryptdSpawnPath with mongoc_auto_encryption_opts_set_extra().

bson_t *extra = BCON_NEW ("mongocryptdSpawnPath", "/path/to/mongocryptd");
mongoc_auto_encryption_opts_set_extra (opts, extra);

To control the logging output of mongocryptd pass mongocryptdSpawnArgs to mongoc_auto_encryption_opts_set_extra():

bson_t *extra = BCON_NEW ("mongocryptdSpawnArgs",
   "[", "--logpath=/path/to/mongocryptd.log", "--logappend", "]");
mongoc_auto_encryption_opts_set_extra (opts, extra);

If your application wishes to manage the mongocryptd process manually, it is possible to disable spawning mongocryptd:

bson_t *extra = BCON_NEW ("mongocryptdBypassSpawn",
   BCON_BOOL(true), "mongocryptdURI", "mongodb://localhost:27020");
mongoc_auto_encryption_opts_set_extra (opts, extra);

mongocryptd is only responsible for supporting automatic Client-Side Encryption in the driver and does not itself perform any encryption or decryption.

Automatic Client-Side Field Level Encryption

Automatic Client-Side Encryption is enabled by calling mongoc_client_enable_auto_encryption() on a mongoc_client_t. The following examples show how to set up automatic client-side field level encryption using mongoc_client_encryption_t to create a new encryption data key.

Note

Automatic client-side field level encryption requires MongoDB 4.2 enterprise or a MongoDB 4.2 Atlas cluster. The community version of the server supports automatic decryption as well as Explicit Encryption.

Providing Local Automatic Encryption Rules

The following example shows how to specify automatic encryption rules using a schema map set with mongoc_auto_encryption_opts_set_schema_map(). The automatic encryption rules are expressed using a strict subset of the JSON Schema syntax.

Supplying a 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.

JSON Schemas supplied in the schema map only apply to configuring automatic client-side field level encryption. Other validation rules in the JSON schema will not be enforced by the driver and will result in an error:

client-side-encryption-schema-map.c
#include <mongoc/mongoc.h>
#include <stdio.h>
#include <stdlib.h>

#include "client-side-encryption-helpers.h"

/* Helper method to create a new data key in the key vault, a schema to use that
 * key, and writes the schema to a file for later use. */
static bool
create_schema_file (bson_t *kms_providers,
                    const char *keyvault_db,
                    const char *keyvault_coll,
                    mongoc_client_t *keyvault_client,
                    bson_error_t *error)
{
   mongoc_client_encryption_t *client_encryption = NULL;
   mongoc_client_encryption_opts_t *client_encryption_opts = NULL;
   mongoc_client_encryption_datakey_opts_t *datakey_opts = NULL;
   bson_value_t datakey_id = {0};
   char *keyaltnames[] = {"mongoc_encryption_example_1"};
   bson_t *schema = NULL;
   char *schema_string = NULL;
   size_t schema_string_len;
   FILE *outfile = NULL;
   bool ret = false;

   client_encryption_opts = mongoc_client_encryption_opts_new ();
   mongoc_client_encryption_opts_set_kms_providers (client_encryption_opts,
                                                    kms_providers);
   mongoc_client_encryption_opts_set_keyvault_namespace (
      client_encryption_opts, keyvault_db, keyvault_coll);
   mongoc_client_encryption_opts_set_keyvault_client (client_encryption_opts,
                                                      keyvault_client);

   client_encryption =
      mongoc_client_encryption_new (client_encryption_opts, error);
   if (!client_encryption) {
      goto fail;
   }

   /* Create a new data key and json schema for the encryptedField.
    * https://dochub.mongodb.org/core/client-side-field-level-encryption-automatic-encryption-rules
    */
   datakey_opts = mongoc_client_encryption_datakey_opts_new ();
   mongoc_client_encryption_datakey_opts_set_keyaltnames (
      datakey_opts, keyaltnames, 1);
   if (!mongoc_client_encryption_create_datakey (
          client_encryption, "local", datakey_opts, &datakey_id, error)) {
      goto fail;
   }

   /* Create a schema describing that "encryptedField" is a string encrypted
    * with the newly created data key using deterministic encryption. */
   schema = BCON_NEW ("properties",
                      "{",
                      "encryptedField",
                      "{",
                      "encrypt",
                      "{",
                      "keyId",
                      "[",
                      BCON_BIN (datakey_id.value.v_binary.subtype,
                                datakey_id.value.v_binary.data,
                                datakey_id.value.v_binary.data_len),
                      "]",
                      "bsonType",
                      "string",
                      "algorithm",
                      MONGOC_AEAD_AES_256_CBC_HMAC_SHA_512_DETERMINISTIC,
                      "}",
                      "}",
                      "}",
                      "bsonType",
                      "object");

   /* Use canonical JSON so that other drivers and tools will be
    * able to parse the MongoDB extended JSON file. */
   schema_string = bson_as_canonical_extended_json (schema, &schema_string_len);
   outfile = fopen ("jsonSchema.json", "w");
   if (0 == fwrite (schema_string, sizeof (char), schema_string_len, outfile)) {
      fprintf (stderr, "failed to write to file\n");
      goto fail;
   }

   ret = true;
fail:
   mongoc_client_encryption_destroy (client_encryption);
   mongoc_client_encryption_datakey_opts_destroy (datakey_opts);
   mongoc_client_encryption_opts_destroy (client_encryption_opts);
   bson_free (schema_string);
   bson_destroy (schema);
   bson_value_destroy (&datakey_id);
   if (outfile) {
      fclose (outfile);
   }
   return true;
}

/* This example demonstrates how to use automatic encryption with a client-side
 * schema map using the enterprise version of MongoDB */
int
main (int argc, char **argv)
{
/* The collection used to store the encryption data keys. */
#define KEYVAULT_DB "encryption"
#define KEYVAULT_COLL "__libmongocTestKeyVault"
/* The collection used to store the encrypted documents in this example. */
#define ENCRYPTED_DB "test"
#define ENCRYPTED_COLL "coll"

   int exit_status = EXIT_FAILURE;
   bool ret;
   uint8_t *local_masterkey = NULL;
   uint32_t local_masterkey_len;
   bson_t *kms_providers = NULL;
   bson_error_t error = {0};
   bson_t *index_keys = NULL;
   char *index_name = NULL;
   bson_t *create_index_cmd = NULL;
   bson_json_reader_t *reader = NULL;
   bson_t schema = BSON_INITIALIZER;
   bson_t *schema_map = NULL;

   /* The MongoClient used to access the key vault (keyvault_namespace). */
   mongoc_client_t *keyvault_client = NULL;
   mongoc_collection_t *keyvault_coll = NULL;
   mongoc_auto_encryption_opts_t *auto_encryption_opts = NULL;
   mongoc_client_t *client = NULL;
   mongoc_collection_t *coll = NULL;
   bson_t *to_insert = NULL;
   mongoc_client_t *unencrypted_client = NULL;
   mongoc_collection_t *unencrypted_coll = NULL;

   mongoc_init ();

   /* Configure the master key. This must be the same master key that was used
    * to create the encryption key. */
   local_masterkey =
      hex_to_bin (getenv ("LOCAL_MASTERKEY"), &local_masterkey_len);
   if (!local_masterkey || local_masterkey_len != 96) {
      fprintf (stderr,
               "Specify LOCAL_MASTERKEY environment variable as a "
               "secure random 96 byte hex value.\n");
      goto fail;
   }

   kms_providers = BCON_NEW ("local",
                             "{",
                             "key",
                             BCON_BIN (0, local_masterkey, local_masterkey_len),
                             "}");

   /* Set up the key vault for this example. */
   keyvault_client = mongoc_client_new (
      "mongodb://localhost/?appname=client-side-encryption-keyvault");
   keyvault_coll = mongoc_client_get_collection (
      keyvault_client, KEYVAULT_DB, KEYVAULT_COLL);
   mongoc_collection_drop (keyvault_coll, NULL);

   /* Create a unique index to ensure that two data keys cannot share the same
    * keyAltName. This is recommended practice for the key vault. */
   index_keys = BCON_NEW ("keyAltNames", BCON_INT32 (1));
   index_name = mongoc_collection_keys_to_index_string (index_keys);
   create_index_cmd = BCON_NEW ("createIndexes",
                                KEYVAULT_COLL,
                                "indexes",
                                "[",
                                "{",
                                "key",
                                BCON_DOCUMENT (index_keys),
                                "name",
                                index_name,
                                "unique",
                                BCON_BOOL (true),
                                "partialFilterExpression",
                                "{",
                                "keyAltNames",
                                "{",
                                "$exists",
                                BCON_BOOL (true),
                                "}",
                                "}",
                                "}",
                                "]");
   ret = mongoc_client_command_simple (keyvault_client,
                                       KEYVAULT_DB,
                                       create_index_cmd,
                                       NULL /* read prefs */,
                                       NULL /* reply */,
                                       &error);

   if (!ret) {
      goto fail;
   }

   /* Create a new data key and a schema using it for encryption. Save the
    * schema to the file jsonSchema.json */
   ret = create_schema_file (
      kms_providers, KEYVAULT_DB, KEYVAULT_COLL, keyvault_client, &error);

   if (!ret) {
      goto fail;
   }

   /* Load the JSON Schema and construct the local schema_map option. */
   reader = bson_json_reader_new_from_file ("jsonSchema.json", &error);
   if (!reader) {
      goto fail;
   }

   bson_json_reader_read (reader, &schema, &error);

   /* Construct the schema map, mapping the namespace of the collection to the
    * schema describing encryption. */
   schema_map =
      BCON_NEW (ENCRYPTED_DB "." ENCRYPTED_COLL, BCON_DOCUMENT (&schema));

   auto_encryption_opts = mongoc_auto_encryption_opts_new ();
   mongoc_auto_encryption_opts_set_keyvault_client (auto_encryption_opts,
                                                    keyvault_client);
   mongoc_auto_encryption_opts_set_keyvault_namespace (
      auto_encryption_opts, KEYVAULT_DB, KEYVAULT_COLL);
   mongoc_auto_encryption_opts_set_kms_providers (auto_encryption_opts,
                                                  kms_providers);
   mongoc_auto_encryption_opts_set_schema_map (auto_encryption_opts,
                                               schema_map);

   client =
      mongoc_client_new ("mongodb://localhost/?appname=client-side-encryption");

   /* Enable automatic encryption. It will determine that encryption is
    * necessary from the schema map instead of relying on the server to provide
    * a schema. */
   ret = mongoc_client_enable_auto_encryption (
      client, auto_encryption_opts, &error);
   if (!ret) {
      goto fail;
   }

   coll = mongoc_client_get_collection (client, ENCRYPTED_DB, ENCRYPTED_COLL);

   /* Clear old data */
   mongoc_collection_drop (coll, NULL);

   to_insert = BCON_NEW ("encryptedField", "123456789");
   ret = mongoc_collection_insert_one (
      coll, to_insert, NULL /* opts */, NULL /* reply */, &error);
   if (!ret) {
      goto fail;
   }
   printf ("decrypted document: ");
   if (!print_one_document (coll, &error)) {
      goto fail;
   }
   printf ("\n");

   unencrypted_client = mongoc_client_new (
      "mongodb://localhost/?appname=client-side-encryption-unencrypted");
   unencrypted_coll = mongoc_client_get_collection (
      unencrypted_client, ENCRYPTED_DB, ENCRYPTED_COLL);
   printf ("encrypted document: ");
   if (!print_one_document (unencrypted_coll, &error)) {
      goto fail;
   }
   printf ("\n");

   exit_status = EXIT_SUCCESS;
fail:
   if (error.code) {
      fprintf (stderr, "error: %s\n", error.message);
   }

   bson_free (local_masterkey);
   bson_destroy (kms_providers);
   mongoc_collection_destroy (keyvault_coll);
   bson_destroy (index_keys);
   bson_free (index_name);
   bson_destroy (create_index_cmd);
   bson_json_reader_destroy (reader);
   mongoc_auto_encryption_opts_destroy (auto_encryption_opts);
   mongoc_collection_destroy (coll);
   mongoc_client_destroy (client);
   bson_destroy (to_insert);
   mongoc_collection_destroy (unencrypted_coll);
   mongoc_client_destroy (unencrypted_client);
   mongoc_client_destroy (keyvault_client);
   bson_destroy (&schema);
   bson_destroy (schema_map);
   mongoc_cleanup ();
   return exit_status;
}

Server-Side Field Level Encryption Enforcement

The MongoDB 4.2 server supports using schema validation to enforce encryption of specific fields in a collection. This schema validation will prevent an application from inserting unencrypted values for any fields marked with the “encrypt” JSON schema keyword.

The following example shows how to set up automatic client-side field level encryption using mongoc_client_encryption_t to create a new encryption data key and create a collection with the Automatic Encryption JSON Schema Syntax:

client-side-encryption-server-schema.c
#include <mongoc/mongoc.h>
#include <stdio.h>
#include <stdlib.h>

#include "client-side-encryption-helpers.h"

/* Helper method to create and return a JSON schema to use for encryption.
The caller will use the returned schema for server-side encryption validation.
*/
static bson_t *
create_schema (bson_t *kms_providers,
               const char *keyvault_db,
               const char *keyvault_coll,
               mongoc_client_t *keyvault_client,
               bson_error_t *error)
{
   mongoc_client_encryption_t *client_encryption = NULL;
   mongoc_client_encryption_opts_t *client_encryption_opts = NULL;
   mongoc_client_encryption_datakey_opts_t *datakey_opts = NULL;
   bson_value_t datakey_id = {0};
   char *keyaltnames[] = {"mongoc_encryption_example_2"};
   bson_t *schema = NULL;

   client_encryption_opts = mongoc_client_encryption_opts_new ();
   mongoc_client_encryption_opts_set_kms_providers (client_encryption_opts,
                                                    kms_providers);
   mongoc_client_encryption_opts_set_keyvault_namespace (
      client_encryption_opts, keyvault_db, keyvault_coll);
   mongoc_client_encryption_opts_set_keyvault_client (client_encryption_opts,
                                                      keyvault_client);

   client_encryption =
      mongoc_client_encryption_new (client_encryption_opts, error);
   if (!client_encryption) {
      goto fail;
   }

   /* Create a new data key and json schema for the encryptedField.
    * https://dochub.mongodb.org/core/client-side-field-level-encryption-automatic-encryption-rules
    */
   datakey_opts = mongoc_client_encryption_datakey_opts_new ();
   mongoc_client_encryption_datakey_opts_set_keyaltnames (
      datakey_opts, keyaltnames, 1);
   if (!mongoc_client_encryption_create_datakey (
          client_encryption, "local", datakey_opts, &datakey_id, error)) {
      goto fail;
   }

   /* Create a schema describing that "encryptedField" is a string encrypted
    * with the newly created data key using deterministic encryption. */
   schema = BCON_NEW ("properties",
                      "{",
                      "encryptedField",
                      "{",
                      "encrypt",
                      "{",
                      "keyId",
                      "[",
                      BCON_BIN (datakey_id.value.v_binary.subtype,
                                datakey_id.value.v_binary.data,
                                datakey_id.value.v_binary.data_len),
                      "]",
                      "bsonType",
                      "string",
                      "algorithm",
                      MONGOC_AEAD_AES_256_CBC_HMAC_SHA_512_DETERMINISTIC,
                      "}",
                      "}",
                      "}",
                      "bsonType",
                      "object");

fail:
   mongoc_client_encryption_destroy (client_encryption);
   mongoc_client_encryption_datakey_opts_destroy (datakey_opts);
   mongoc_client_encryption_opts_destroy (client_encryption_opts);
   bson_value_destroy (&datakey_id);
   return schema;
}

/* This example demonstrates how to use automatic encryption with a server-side
 * schema using the enterprise version of MongoDB */
int
main (int argc, char **argv)
{
/* The collection used to store the encryption data keys. */
#define KEYVAULT_DB "encryption"
#define KEYVAULT_COLL "__libmongocTestKeyVault"
/* The collection used to store the encrypted documents in this example. */
#define ENCRYPTED_DB "test"
#define ENCRYPTED_COLL "coll"

   int exit_status = EXIT_FAILURE;
   bool ret;
   uint8_t *local_masterkey = NULL;
   uint32_t local_masterkey_len;
   bson_t *kms_providers = NULL;
   bson_error_t error = {0};
   bson_t *index_keys = NULL;
   char *index_name = NULL;
   bson_t *create_index_cmd = NULL;
   bson_json_reader_t *reader = NULL;
   bson_t *schema = NULL;

   /* The MongoClient used to access the key vault (keyvault_namespace). */
   mongoc_client_t *keyvault_client = NULL;
   mongoc_collection_t *keyvault_coll = NULL;
   mongoc_auto_encryption_opts_t *auto_encryption_opts = NULL;
   mongoc_client_t *client = NULL;
   mongoc_collection_t *coll = NULL;
   bson_t *to_insert = NULL;
   mongoc_client_t *unencrypted_client = NULL;
   mongoc_collection_t *unencrypted_coll = NULL;
   bson_t *create_cmd = NULL;
   bson_t *create_cmd_opts = NULL;
   mongoc_write_concern_t *wc = NULL;

   mongoc_init ();

   /* Configure the master key. This must be the same master key that was used
    * to create
    * the encryption key. */
   local_masterkey =
      hex_to_bin (getenv ("LOCAL_MASTERKEY"), &local_masterkey_len);
   if (!local_masterkey || local_masterkey_len != 96) {
      fprintf (stderr,
               "Specify LOCAL_MASTERKEY environment variable as a "
               "secure random 96 byte hex value.\n");
      goto fail;
   }

   kms_providers = BCON_NEW ("local",
                             "{",
                             "key",
                             BCON_BIN (0, local_masterkey, local_masterkey_len),
                             "}");

   /* Set up the key vault for this example. */
   keyvault_client = mongoc_client_new (
      "mongodb://localhost/?appname=client-side-encryption-keyvault");
   keyvault_coll = mongoc_client_get_collection (
      keyvault_client, KEYVAULT_DB, KEYVAULT_COLL);
   mongoc_collection_drop (keyvault_coll, NULL);

   /* Create a unique index to ensure that two data keys cannot share the same
    * keyAltName. This is recommended practice for the key vault. */
   index_keys = BCON_NEW ("keyAltNames", BCON_INT32 (1));
   index_name = mongoc_collection_keys_to_index_string (index_keys);
   create_index_cmd = BCON_NEW ("createIndexes",
                                KEYVAULT_COLL,
                                "indexes",
                                "[",
                                "{",
                                "key",
                                BCON_DOCUMENT (index_keys),
                                "name",
                                index_name,
                                "unique",
                                BCON_BOOL (true),
                                "partialFilterExpression",
                                "{",
                                "keyAltNames",
                                "{",
                                "$exists",
                                BCON_BOOL (true),
                                "}",
                                "}",
                                "}",
                                "]");
   ret = mongoc_client_command_simple (keyvault_client,
                                       KEYVAULT_DB,
                                       create_index_cmd,
                                       NULL /* read prefs */,
                                       NULL /* reply */,
                                       &error);

   if (!ret) {
      goto fail;
   }

   auto_encryption_opts = mongoc_auto_encryption_opts_new ();
   mongoc_auto_encryption_opts_set_keyvault_client (auto_encryption_opts,
                                                    keyvault_client);
   mongoc_auto_encryption_opts_set_keyvault_namespace (
      auto_encryption_opts, KEYVAULT_DB, KEYVAULT_COLL);
   mongoc_auto_encryption_opts_set_kms_providers (auto_encryption_opts,
                                                  kms_providers);
   schema = create_schema (
      kms_providers, KEYVAULT_DB, KEYVAULT_COLL, keyvault_client, &error);

   if (!schema) {
      goto fail;
   }

   client =
      mongoc_client_new ("mongodb://localhost/?appname=client-side-encryption");
   ret = mongoc_client_enable_auto_encryption (
      client, auto_encryption_opts, &error);
   if (!ret) {
      goto fail;
   }

   coll = mongoc_client_get_collection (client, ENCRYPTED_DB, ENCRYPTED_COLL);

   /* Clear old data */
   mongoc_collection_drop (coll, NULL);

   /* Create the collection with the encryption JSON Schema. */
   create_cmd = BCON_NEW ("create",
                          ENCRYPTED_COLL,
                          "validator",
                          "{",
                          "$jsonSchema",
                          BCON_DOCUMENT (schema),
                          "}");
   wc = mongoc_write_concern_new ();
   mongoc_write_concern_set_wmajority (wc, 0);
   create_cmd_opts = bson_new ();
   mongoc_write_concern_append (wc, create_cmd_opts);
   ret = mongoc_client_command_with_opts (client,
                                          ENCRYPTED_DB,
                                          create_cmd,
                                          NULL /* read prefs */,
                                          create_cmd_opts,
                                          NULL /* reply */,
                                          &error);
   if (!ret) {
      goto fail;
   }

   to_insert = BCON_NEW ("encryptedField", "123456789");
   ret = mongoc_collection_insert_one (
      coll, to_insert, NULL /* opts */, NULL /* reply */, &error);
   if (!ret) {
      goto fail;
   }
   printf ("decrypted document: ");
   if (!print_one_document (coll, &error)) {
      goto fail;
   }
   printf ("\n");

   unencrypted_client = mongoc_client_new (
      "mongodb://localhost/?appname=client-side-encryption-unencrypted");
   unencrypted_coll = mongoc_client_get_collection (
      unencrypted_client, ENCRYPTED_DB, ENCRYPTED_COLL);
   printf ("encrypted document: ");
   if (!print_one_document (unencrypted_coll, &error)) {
      goto fail;
   }
   printf ("\n");

   /* Expect a server-side error if inserting with the unencrypted collection.
    */
   ret = mongoc_collection_insert_one (
      unencrypted_coll, to_insert, NULL /* opts */, NULL /* reply */, &error);
   if (!ret) {
      printf ("insert with unencrypted collection failed: %s\n", error.message);
      memset (&error, 0, sizeof (error));
   }

   exit_status = EXIT_SUCCESS;
fail:
   if (error.code) {
      fprintf (stderr, "error: %s\n", error.message);
   }

   bson_free (local_masterkey);
   bson_destroy (kms_providers);
   mongoc_collection_destroy (keyvault_coll);
   bson_destroy (index_keys);
   bson_free (index_name);
   bson_destroy (create_index_cmd);
   bson_json_reader_destroy (reader);
   mongoc_auto_encryption_opts_destroy (auto_encryption_opts);
   mongoc_collection_destroy (coll);
   mongoc_client_destroy (client);
   bson_destroy (to_insert);
   mongoc_collection_destroy (unencrypted_coll);
   mongoc_client_destroy (unencrypted_client);
   mongoc_client_destroy (keyvault_client);
   bson_destroy (schema);
   bson_destroy (create_cmd);
   bson_destroy (create_cmd_opts);
   mongoc_write_concern_destroy (wc);

   mongoc_cleanup ();
   return exit_status;
}

Explicit Encryption

Explicit encryption is a MongoDB community feature and does not use the mongocryptd process. Explicit encryption is provided by the mongoc_client_encryption_t class, for example:

client-side-encryption-explicit.c
#include <mongoc/mongoc.h>
#include <stdio.h>
#include <stdlib.h>

#include "client-side-encryption-helpers.h"

/* This example demonstrates how to use explicit encryption and decryption using
 * the community version of MongoDB */
int
main (int argc, char **argv)
{
/* The collection used to store the encryption data keys. */
#define KEYVAULT_DB "encryption"
#define KEYVAULT_COLL "__libmongocTestKeyVault"
/* The collection used to store the encrypted documents in this example. */
#define ENCRYPTED_DB "test"
#define ENCRYPTED_COLL "coll"

   int exit_status = EXIT_FAILURE;
   bool ret;
   uint8_t *local_masterkey = NULL;
   uint32_t local_masterkey_len;
   bson_t *kms_providers = NULL;
   bson_error_t error = {0};
   bson_t *index_keys = NULL;
   char *index_name = NULL;
   bson_t *create_index_cmd = NULL;
   bson_t *schema = NULL;
   mongoc_client_t *client = NULL;
   mongoc_collection_t *coll = NULL;
   mongoc_collection_t *keyvault_coll = NULL;
   bson_t *to_insert = NULL;
   bson_t *create_cmd = NULL;
   bson_t *create_cmd_opts = NULL;
   mongoc_write_concern_t *wc = NULL;
   mongoc_client_encryption_t *client_encryption = NULL;
   mongoc_client_encryption_opts_t *client_encryption_opts = NULL;
   mongoc_client_encryption_datakey_opts_t *datakey_opts = NULL;
   char *keyaltnames[] = {"mongoc_encryption_example_3"};
   bson_value_t datakey_id = {0};
   bson_value_t encrypted_field = {0};
   bson_value_t to_encrypt = {0};
   mongoc_client_encryption_encrypt_opts_t *encrypt_opts = NULL;
   bson_value_t decrypted = {0};

   mongoc_init ();

   /* Configure the master key. This must be the same master key that was used
    * to create the encryption key. */
   local_masterkey =
      hex_to_bin (getenv ("LOCAL_MASTERKEY"), &local_masterkey_len);
   if (!local_masterkey || local_masterkey_len != 96) {
      fprintf (stderr,
               "Specify LOCAL_MASTERKEY environment variable as a "
               "secure random 96 byte hex value.\n");
      goto fail;
   }

   kms_providers = BCON_NEW ("local",
                             "{",
                             "key",
                             BCON_BIN (0, local_masterkey, local_masterkey_len),
                             "}");

   /* The mongoc_client_t used to read/write application data. */
   client =
      mongoc_client_new ("mongodb://localhost/?appname=client-side-encryption");
   coll = mongoc_client_get_collection (client, ENCRYPTED_DB, ENCRYPTED_COLL);

   /* Clear old data */
   mongoc_collection_drop (coll, NULL);

   /* Set up the key vault for this example. */
   keyvault_coll =
      mongoc_client_get_collection (client, KEYVAULT_DB, KEYVAULT_COLL);
   mongoc_collection_drop (keyvault_coll, NULL);

   /* Create a unique index to ensure that two data keys cannot share the same
    * keyAltName. This is recommended practice for the key vault. */
   index_keys = BCON_NEW ("keyAltNames", BCON_INT32 (1));
   index_name = mongoc_collection_keys_to_index_string (index_keys);
   create_index_cmd = BCON_NEW ("createIndexes",
                                KEYVAULT_COLL,
                                "indexes",
                                "[",
                                "{",
                                "key",
                                BCON_DOCUMENT (index_keys),
                                "name",
                                index_name,
                                "unique",
                                BCON_BOOL (true),
                                "partialFilterExpression",
                                "{",
                                "keyAltNames",
                                "{",
                                "$exists",
                                BCON_BOOL (true),
                                "}",
                                "}",
                                "}",
                                "]");
   ret = mongoc_client_command_simple (client,
                                       KEYVAULT_DB,
                                       create_index_cmd,
                                       NULL /* read prefs */,
                                       NULL /* reply */,
                                       &error);

   if (!ret) {
      goto fail;
   }

   client_encryption_opts = mongoc_client_encryption_opts_new ();
   mongoc_client_encryption_opts_set_kms_providers (client_encryption_opts,
                                                    kms_providers);
   mongoc_client_encryption_opts_set_keyvault_namespace (
      client_encryption_opts, KEYVAULT_DB, KEYVAULT_COLL);

   /* Set a mongoc_client_t to use for reading/writing to the key vault. This
    * can be the same mongoc_client_t used by the main application. */
   mongoc_client_encryption_opts_set_keyvault_client (client_encryption_opts,
                                                      client);
   client_encryption =
      mongoc_client_encryption_new (client_encryption_opts, &error);
   if (!client_encryption) {
      goto fail;
   }

   /* Create a new data key for the encryptedField.
    * https://dochub.mongodb.org/core/client-side-field-level-encryption-automatic-encryption-rules
    */
   datakey_opts = mongoc_client_encryption_datakey_opts_new ();
   mongoc_client_encryption_datakey_opts_set_keyaltnames (
      datakey_opts, keyaltnames, 1);
   if (!mongoc_client_encryption_create_datakey (
          client_encryption, "local", datakey_opts, &datakey_id, &error)) {
      goto fail;
   }

   /* Explicitly encrypt a field */
   encrypt_opts = mongoc_client_encryption_encrypt_opts_new ();
   mongoc_client_encryption_encrypt_opts_set_algorithm (
      encrypt_opts, MONGOC_AEAD_AES_256_CBC_HMAC_SHA_512_DETERMINISTIC);
   mongoc_client_encryption_encrypt_opts_set_keyid (encrypt_opts, &datakey_id);
   to_encrypt.value_type = BSON_TYPE_UTF8;
   to_encrypt.value.v_utf8.str = "123456789";
   to_encrypt.value.v_utf8.len = strlen (to_encrypt.value.v_utf8.str);

   ret = mongoc_client_encryption_encrypt (
      client_encryption, &to_encrypt, encrypt_opts, &encrypted_field, &error);
   if (!ret) {
      goto fail;
   }

   to_insert = bson_new ();
   BSON_APPEND_VALUE (to_insert, "encryptedField", &encrypted_field);
   ret = mongoc_collection_insert_one (
      coll, to_insert, NULL /* opts */, NULL /* reply */, &error);
   if (!ret) {
      goto fail;
   }

   printf ("encrypted document: ");
   if (!print_one_document (coll, &error)) {
      goto fail;
   }
   printf ("\n");

   /* Explicitly decrypt a field */
   ret = mongoc_client_encryption_decrypt (
      client_encryption, &encrypted_field, &decrypted, &error);
   if (!ret) {
      goto fail;
   }
   printf ("decrypted value: %s\n", decrypted.value.v_utf8.str);

   exit_status = EXIT_SUCCESS;
fail:
   if (error.code) {
      fprintf (stderr, "error: %s\n", error.message);
   }

   bson_free (local_masterkey);
   bson_destroy (kms_providers);
   mongoc_collection_destroy (keyvault_coll);
   bson_destroy (index_keys);
   bson_free (index_name);
   bson_destroy (create_index_cmd);
   mongoc_collection_destroy (coll);
   mongoc_client_destroy (client);
   bson_destroy (to_insert);
   bson_destroy (schema);
   bson_destroy (create_cmd);
   bson_destroy (create_cmd_opts);
   mongoc_write_concern_destroy (wc);
   mongoc_client_encryption_destroy (client_encryption);
   mongoc_client_encryption_datakey_opts_destroy (datakey_opts);
   mongoc_client_encryption_opts_destroy (client_encryption_opts);
   bson_value_destroy (&encrypted_field);
   mongoc_client_encryption_encrypt_opts_destroy (encrypt_opts);
   bson_value_destroy (&decrypted);
   bson_value_destroy (&datakey_id);

   mongoc_cleanup ();
   return exit_status;
}

Explicit Encryption with Automatic Decryption

Although automatic encryption requires MongoDB 4.2 enterprise or a MongoDB 4.2 Atlas cluster, automatic decryption is supported for all users. To configure automatic decryption without automatic encryption set bypass_auto_encryption=True in mongoc_auto_encryption_opts_t:

client-side-encryption-auto-decryption.c
#include <mongoc/mongoc.h>
#include <stdio.h>
#include <stdlib.h>

#include "client-side-encryption-helpers.h"

/* This example demonstrates how to set up automatic decryption without
 * automatic encryption using the community version of MongoDB */
int
main (int argc, char **argv)
{
/* The collection used to store the encryption data keys. */
#define KEYVAULT_DB "encryption"
#define KEYVAULT_COLL "__libmongocTestKeyVault"
/* The collection used to store the encrypted documents in this example. */
#define ENCRYPTED_DB "test"
#define ENCRYPTED_COLL "coll"

   int exit_status = EXIT_FAILURE;
   bool ret;
   uint8_t *local_masterkey = NULL;
   uint32_t local_masterkey_len;
   bson_t *kms_providers = NULL;
   bson_error_t error = {0};
   bson_t *index_keys = NULL;
   char *index_name = NULL;
   bson_t *create_index_cmd = NULL;
   bson_t *schema = NULL;
   mongoc_client_t *client = NULL;
   mongoc_collection_t *coll = NULL;
   mongoc_collection_t *keyvault_coll = NULL;
   bson_t *to_insert = NULL;
   bson_t *create_cmd = NULL;
   bson_t *create_cmd_opts = NULL;
   mongoc_write_concern_t *wc = NULL;
   mongoc_client_encryption_t *client_encryption = NULL;
   mongoc_client_encryption_opts_t *client_encryption_opts = NULL;
   mongoc_client_encryption_datakey_opts_t *datakey_opts = NULL;
   char *keyaltnames[] = {"mongoc_encryption_example_4"};
   bson_value_t datakey_id = {0};
   bson_value_t encrypted_field = {0};
   bson_value_t to_encrypt = {0};
   mongoc_client_encryption_encrypt_opts_t *encrypt_opts = NULL;
   bson_value_t decrypted = {0};
   mongoc_auto_encryption_opts_t *auto_encryption_opts = NULL;
   mongoc_client_t *unencrypted_client = NULL;
   mongoc_collection_t *unencrypted_coll = NULL;

   mongoc_init ();

   /* Configure the master key. This must be the same master key that was used
    * to create the encryption key. */
   local_masterkey =
      hex_to_bin (getenv ("LOCAL_MASTERKEY"), &local_masterkey_len);
   if (!local_masterkey || local_masterkey_len != 96) {
      fprintf (stderr,
               "Specify LOCAL_MASTERKEY environment variable as a "
               "secure random 96 byte hex value.\n");
      goto fail;
   }

   kms_providers = BCON_NEW ("local",
                             "{",
                             "key",
                             BCON_BIN (0, local_masterkey, local_masterkey_len),
                             "}");

   client =
      mongoc_client_new ("mongodb://localhost/?appname=client-side-encryption");
   auto_encryption_opts = mongoc_auto_encryption_opts_new ();
   mongoc_auto_encryption_opts_set_keyvault_namespace (
      auto_encryption_opts, KEYVAULT_DB, KEYVAULT_COLL);
   mongoc_auto_encryption_opts_set_kms_providers (auto_encryption_opts,
                                                  kms_providers);

   /* Setting bypass_auto_encryption to true disables automatic encryption but
    * keeps the automatic decryption behavior. bypass_auto_encryption will also
    * disable spawning mongocryptd */
   mongoc_auto_encryption_opts_set_bypass_auto_encryption (auto_encryption_opts,
                                                           true);

   /* Once bypass_auto_encryption is set, community users can enable auto
    * encryption on the client. This will, in fact, only perform automatic
    * decryption. */
   ret = mongoc_client_enable_auto_encryption (
      client, auto_encryption_opts, &error);
   if (!ret) {
      goto fail;
   }

   /* Now that automatic decryption is on, we can test it by inserting a
    * document with an explicitly encrypted value into the collection. When we
    * look up the document later, it should be automatically decrypted for us.
    */
   coll = mongoc_client_get_collection (client, ENCRYPTED_DB, ENCRYPTED_COLL);

   /* Clear old data */
   mongoc_collection_drop (coll, NULL);

   /* Set up the key vault for this example. */
   keyvault_coll =
      mongoc_client_get_collection (client, KEYVAULT_DB, KEYVAULT_COLL);
   mongoc_collection_drop (keyvault_coll, NULL);

   /* Create a unique index to ensure that two data keys cannot share the same
    * keyAltName. This is recommended practice for the key vault. */
   index_keys = BCON_NEW ("keyAltNames", BCON_INT32 (1));
   index_name = mongoc_collection_keys_to_index_string (index_keys);
   create_index_cmd = BCON_NEW ("createIndexes",
                                KEYVAULT_COLL,
                                "indexes",
                                "[",
                                "{",
                                "key",
                                BCON_DOCUMENT (index_keys),
                                "name",
                                index_name,
                                "unique",
                                BCON_BOOL (true),
                                "partialFilterExpression",
                                "{",
                                "keyAltNames",
                                "{",
                                "$exists",
                                BCON_BOOL (true),
                                "}",
                                "}",
                                "}",
                                "]");
   ret = mongoc_client_command_simple (client,
                                       KEYVAULT_DB,
                                       create_index_cmd,
                                       NULL /* read prefs */,
                                       NULL /* reply */,
                                       &error);

   if (!ret) {
      goto fail;
   }

   client_encryption_opts = mongoc_client_encryption_opts_new ();
   mongoc_client_encryption_opts_set_kms_providers (client_encryption_opts,
                                                    kms_providers);
   mongoc_client_encryption_opts_set_keyvault_namespace (
      client_encryption_opts, KEYVAULT_DB, KEYVAULT_COLL);

   /* The key vault client is used for reading to/from the key vault. This can
    * be the same mongoc_client_t used by the application. */
   mongoc_client_encryption_opts_set_keyvault_client (client_encryption_opts,
                                                      client);
   client_encryption =
      mongoc_client_encryption_new (client_encryption_opts, &error);
   if (!client_encryption) {
      goto fail;
   }

   /* Create a new data key for the encryptedField.
    * https://dochub.mongodb.org/core/client-side-field-level-encryption-automatic-encryption-rules
    */
   datakey_opts = mongoc_client_encryption_datakey_opts_new ();
   mongoc_client_encryption_datakey_opts_set_keyaltnames (
      datakey_opts, keyaltnames, 1);
   ret = mongoc_client_encryption_create_datakey (
      client_encryption, "local", datakey_opts, &datakey_id, &error);
   if (!ret) {
      goto fail;
   }

   /* Explicitly encrypt a field. */
   encrypt_opts = mongoc_client_encryption_encrypt_opts_new ();
   mongoc_client_encryption_encrypt_opts_set_algorithm (
      encrypt_opts, MONGOC_AEAD_AES_256_CBC_HMAC_SHA_512_DETERMINISTIC);
   mongoc_client_encryption_encrypt_opts_set_keyaltname (
      encrypt_opts, "mongoc_encryption_example_4");
   to_encrypt.value_type = BSON_TYPE_UTF8;
   to_encrypt.value.v_utf8.str = "123456789";
   to_encrypt.value.v_utf8.len = strlen (to_encrypt.value.v_utf8.str);

   ret = mongoc_client_encryption_encrypt (
      client_encryption, &to_encrypt, encrypt_opts, &encrypted_field, &error);
   if (!ret) {
      goto fail;
   }

   to_insert = bson_new ();
   BSON_APPEND_VALUE (to_insert, "encryptedField", &encrypted_field);
   ret = mongoc_collection_insert_one (
      coll, to_insert, NULL /* opts */, NULL /* reply */, &error);
   if (!ret) {
      goto fail;
   }

   /* When we retrieve the document, any encrypted fields will get automatically
    * decrypted by the driver. */
   printf ("decrypted document: ");
   if (!print_one_document (coll, &error)) {
      goto fail;
   }
   printf ("\n");

   unencrypted_client =
      mongoc_client_new ("mongodb://localhost/?appname=client-side-encryption");
   unencrypted_coll = mongoc_client_get_collection (
      unencrypted_client, ENCRYPTED_DB, ENCRYPTED_COLL);

   printf ("encrypted document: ");
   if (!print_one_document (unencrypted_coll, &error)) {
      goto fail;
   }
   printf ("\n");

   exit_status = EXIT_SUCCESS;
fail:
   if (error.code) {
      fprintf (stderr, "error: %s\n", error.message);
   }

   bson_free (local_masterkey);
   bson_destroy (kms_providers);
   mongoc_collection_destroy (keyvault_coll);
   bson_destroy (index_keys);
   bson_free (index_name);
   bson_destroy (create_index_cmd);
   mongoc_collection_destroy (coll);
   mongoc_client_destroy (client);
   bson_destroy (to_insert);
   bson_destroy (schema);
   bson_destroy (create_cmd);
   bson_destroy (create_cmd_opts);
   mongoc_write_concern_destroy (wc);
   mongoc_client_encryption_destroy (client_encryption);
   mongoc_client_encryption_datakey_opts_destroy (datakey_opts);
   mongoc_client_encryption_opts_destroy (client_encryption_opts);
   bson_value_destroy (&encrypted_field);
   mongoc_client_encryption_encrypt_opts_destroy (encrypt_opts);
   bson_value_destroy (&decrypted);
   bson_value_destroy (&datakey_id);
   mongoc_collection_destroy (unencrypted_coll);
   mongoc_client_destroy (unencrypted_client);
   mongoc_auto_encryption_opts_destroy (auto_encryption_opts);

   mongoc_cleanup ();
   return exit_status;
}