Encryption plugins in MariaDB are used for the data at rest encryption feature. They are responsible for both key management and for the actual encryption and decryption of data.

Conceptually the responsibilities are split like this — when encrypting objects (now tables, later may be more) the user specifies the logical security domain (encryption key identifier) for the object. The plugin implements the actual security policy for this particular key identifier. The plugin defines what encryption algorithm and key length to use, how often the key should be changed, and so on. All this can depend on the encryption key identifier. For example, a plugin can provide two key identifiers — 10, “low security” having short keys with no rotation and a fast encryption algorithm, and 20, “high security” with long keys, which are changed often, and a slower but more secure encryption algorithm. The user decides which tables should use which key identifier. The user knows his data and specifies on the high level what security policy to use. The plugin implements this security policy.

Two key identifiers are reserved for use inside MariaDB. Key id 1 is used for encrypting system data, like InnoDB redo logs, binary logs, and so on. It must always exist. Key id 2 is used for encrypting temporary data, like temporary files and temporary tables. It is optional, if it does not exist, key id 1 will be used instead.

Encryption Plugin API

The Encryption plugin API was created to allow a plugin to:

  • implement key management, provide encryption keys to the server on request and change them according to internal policies.
  • implement actual data encryption and decryption with the algorithm defined by the plugin.

This is how the API reflects that:

/* returned from get_latest_key_version() */
#define ENCRYPTION_KEY_VERSION_INVALID        (~(unsigned int)0)
#define ENCRYPTION_KEY_NOT_ENCRYPTED          (0)

#define ENCRYPTION_KEY_SYSTEM_DATA             1
#define ENCRYPTION_KEY_TEMPORARY_DATA          2

/* returned from get_key()  */
#define ENCRYPTION_KEY_BUFFER_TOO_SMALL    (100)

#define ENCRYPTION_FLAG_DECRYPT     0
#define ENCRYPTION_FLAG_ENCRYPT     1
#define ENCRYPTION_FLAG_NOPAD       2

struct st_mariadb_encryption
{
  int interface_version;                        /**< version plugin uses */

  /*********** KEY MANAGEMENT ********************************************/

  /**
    function returning latest key version for a given key id

    @return a version or ENCRYPTION_KEY_VERSION_INVALID to indicate an error.
  */
  unsigned int (*get_latest_key_version)(unsigned int key_id);

  /**
    function returning a key for a key version

    @param key_id       the requested key id
    @param version      the requested key version
    @param key          the key will be stored there. Can be NULL -
                        in which case no key will be returned
    @param key_length   in: key buffer size
                        out: the actual length of the key

    This method can be used to query the key length - the required
    buffer size - by passing key==NULL.

    If the buffer size is less than the key length the content of the
    key buffer is undefined (the plugin is free to partially fill it with
    the key data or leave it untouched).

    @return 0 on success, or
            ENCRYPTION_KEY_VERSION_INVALID, ENCRYPTION_KEY_BUFFER_TOO_SMALL
            or any other non-zero number for errors
  */
  unsigned int (*get_key)(unsigned int key_id, unsigned int version,
                          unsigned char *key, unsigned int *key_length);

  /*********** ENCRYPTION ************************************************/
  /*
    the caller uses encryption as follows:
      1. create the encryption context object of the crypt_ctx_size() bytes.
      2. initialize it with crypt_ctx_init().
      3. repeat crypt_ctx_update() until there are no more data to encrypt.
      4. write the remaining output bytes and destroy the context object
         with crypt_ctx_finish().
  */

  /**
    returns the size of the encryption context object in bytes
  */
  unsigned int (*crypt_ctx_size)(unsigned int key_id, unsigned int key_version);
  /**
    initializes the encryption context object.
  */
  int (*crypt_ctx_init)(void *ctx, const unsigned char* key, unsigned int klen,
                        const unsigned char* iv, unsigned int ivlen,
                        int flags, unsigned int key_id,
                        unsigned int key_version);
  /**
    processes (encrypts or decrypts) a chunk of data

    writes the output to th dst buffer. note that it might write
    more bytes that were in the input. or less. or none at all.
  */
  int (*crypt_ctx_update)(void *ctx, const unsigned char* src, unsigned int slen,
                          unsigned char* dst, unsigned int* dlen);
  /**
    writes the remaining output bytes and destroys the encryption context

    crypt_ctx_update might've cached part of the output in the context,
    this method will flush these data out.
  */
  int (*crypt_ctx_finish)(void *ctx, unsigned char* dst, unsigned int* dlen);
  /**
    returns the length of the encrypted data

    it returns the exact length, given only the source length.
    which means, this API only supports encryption algorithms where
    the length of the encrypted data only depends on the length of the
    input (a.k.a. compression is not supported).
  */
  unsigned int (*encrypted_length)(unsigned int slen, unsigned int key_id, unsigned int key_version);
};

The first method is used for key rotation. A plugin that doesn't support key rotation — for example, file_key_management — can return a fixed version for any valid key id. Note that it still has to return an error for an invalid key id. The version ENCRYPTION_KEY_NOT_ENCRYPTED means that the data should not be encrypted.

The second method is used for key management, the server uses it to retrieve the key corresponding to a specific key identifier and a specific key version.

The last five methods deal with encryption. Note that they take the key to use and key identifier and version. This is needed because the server can derive a session-specific, user-specific, or a tablespace-specific key from the original encryption key as returned by get_key(), so the key argument doesn't have to match the encryption key as the plugin knows it. On the other hand, the encryption algorithm may depend on the key identifier and version (and in the example_key_management plugin it does) so the plugin needs to know them to be able to encrypt the data.

Encryption methods are optional — if unset (as in the debug_key_management plugin), the server will fall back to AES_CBC.

The MariaDB source tree has three encryption plugins:

file_key_management

Reads encryption keys from a file. Supports encryption key identifiers, does not support encryption key versions. Supports two encryption algorithms and allows user to select which one to use. It's described in detail here.

Versions

VersionStatusIntroduced
1.0StableMariaDB 10.1.18
1.0GammaMariaDB 10.1.13
1.0AlphaMariaDB 10.1.3

example_key_management

Uses random time-based generated keys, ignores key identifiers, supports key versions and key rotation. Uses AES_ECB and AES_CBC as encryption algorithms and changes them automatically together with key versions.

Versions

VersionStatusIntroduced
1.0ExperimentalMariaDB 10.1.3

debug_key_management

Key is generated from the version, user manually controls key rotation. Only supports key identifier 1, uses only AES_CBC.

Versions

VersionStatusIntroduced
1.0ExperimentalMariaDB 10.1.3

aws_key_management

Uses Amazon's key management service. Keys are stored on disk in encrypted form. Keys are decrypted when the server runs using AWS API. Provides different key ids and rotation. See the AWS Key Management Encryption Plugin article for more details.

All these plugins are fairly simple and can serve as good examples of the Encryption plugin API.

Encryption Service

Encryption is generally needed on the very low level inside the storage engine. That is, the storage engine needs to support encryption and have access to the encryption and key management functionality. The usual way for a plugin to access some functionality in the server is via a service. In this case the server provides the Encryption Service for storage engines (and other interested plugins) to use. These service functions are directly hooked into encryption plugin methods (described above).

Service functions are declared as follows:

unsigned int encryption_key_get_latest_version(unsigned int key_id);
unsigned int encryption_key_get(unsigned int key_id, unsigned int key_version,
                                unsigned char* buffer, unsigned int* length);
unsigned int encryption_ctx_size(unsigned int key_id, unsigned int key_version);
int encryption_ctx_init(void *ctx, const unsigned char* key, unsigned int klen,
                        const unsigned char* iv, unsigned int ivlen,
                        int flags, unsigned int key_id, unsigned int key_version);
int encryption_ctx_update(void *ctx, const unsigned char* src, unsigned int slen,
                          unsigned char* dst, unsigned int* dlen);
int encryption_ctx_finish(void *ctx, unsigned char* dst, unsigned int* dlen);
unsigned int encryption_encrypted_length(unsigned int slen, unsigned int key_id, unsigned int key_version);

There are also convenience helpers to check for a key or key version existence and to encrypt or decrypt a block of data with one function call.

unsigned int encryption_key_id_exists(unsigned int id);
unsigned int encryption_key_version_exists(unsigned int id, unsigned int version);
int encryption_crypt(const unsigned char* src, unsigned int slen,
                     unsigned char* dst, unsigned int* dlen,
                     const unsigned char* key, unsigned int klen,
                     const unsigned char* iv, unsigned int ivlen,
                     int flags, unsigned int key_id, unsigned int key_version);

Comments

Comments loading...
Loading