Secure Сell

Secure Сell is a high-level cryptographic service aimed at protecting arbitrary data stored in various types of storages (i.e. databases, filesystem files, document archives, cloud storage, etc.). It provides a simple way of securing your data using strong encryption and data authentication mechanisms, with easy-to-use interfaces for a broad range of use-cases.

Implementing secure storage is often constrained by various practical matters — the ability to store keys, the existence of length-sensitive code bound to the database structure, the need to preserve the structure. To cover a broader range of usage scenarios and to provide the highest security level possible for systems with such constraints, we've designed several types of interfaces and implementations of our secure data container, Secure Cell.

Those interfaces differ slightly in their overall security level and their ease of use because more complicated and slightly less secure ones can cover more constrained environments. The interfaces below are prioritised by their security and ease of use (our subjective and opinionated "hit parade").

Seal Mode

This is (in our opinion) the easiest and the most secure way to protect stored data. All that's required from you is to provide some secret (password, secret key, etc.) and the data proper to the API. The data will then be encrypted and an authentication tag will be appended to the data, meaning the size of the encrypted data will be larger than that of the original data.

The users of this object mode can also bind the data to some context (i.e. database row number), so the decryption of data with incorrect context will fail (even if the secret is correct). This allows establishing cryptographically secure associations between the protected data and its context. In the example with the database row numbers, this will prevent encrypted data from being tampered with by an attacker (who, for example, is forcing the system to accept a wrong hash to check credentials by displacing row numbers or primary key values).

Token Protect mode

This object mode is for cases when underlying storage constraints do not allow the size of the data to grow (so Secure Cell Seal mode described above cannot be used). However, the user has access to a different storage location (i.e. another table in the database) where they can store the needed security parameters.

The Secure Cell object puts authentication tag and other auxiliary information (aka "data token") to a separate buffer, so the user can store it elsewhere while keeping the original encrypted data size. The same token has to be provided along with the correct secret for the data to be decrypted successfully. Since the same security parameters are used (stored in a different location) this object mode has the same security level as Secure Cell Seal mode, but requires slightly more effort from the user. In this mode, the user also has the ability to bind the data to its context.

Context Imprint mode

This object mode is created for environments where the storage constraints do not allow the size of the data to increase and where no auxiliary storage is available. Secure Cell context imprint relies on the user to provide the data context along with the secret to protect the information. Also, no authentication tag is computed or verified. This means the integrity of the data is not enforced, so the overall security level is slightly lower than in the two preceding cases.

NOTE: To ensure the highest security level possible, the user has to supply different context for each encryption invocation of the object for the same secret.

Which Secure Cell mode to chose?

We suggest that you start with analysing your product's requirements. For example, Seal mode works for most cases, but if preserving the cipher text's initial length is crucial for you, it is better to choose from Context Imprint or Token Protect modes.

To make a choice that's better suited to your needs, please see the following table:

Features Seal Token Protect Context Imprint
Preserve text length - +
(separate buffer required for auth tag)
+
User context is required - - +
Enforce integrity
(calculate and verify auth tag)
+ + -
Separate cypher data and auth tag - + -

Implementation details

You can use all the three modes programmatically. The Secure Cell interface is described in src/themis/secure_cell.h.

Seal mode

All the output data (encrypted message, auth tag, iv, etc.) is generated by Themis and is packed into a single output buffer. Error status is returned if decryption is not possible.

/* encrypt */
themis_status_t themis_secure_cell_encrypt_seal(const uint8_t* master_key,
                        const size_t master_key_length,
                        const uint8_t* user_context,
                        const size_t user_context_length,
                        const uint8_t* message,
                        const size_t message_length,
                        uint8_t* encrypted_message,
                        size_t* encrypted_message_length);

/* decrypt */
themis_status_t themis_secure_cell_decrypt_seal(const uint8_t* master_key,
                        const size_t master_key_length,
                        const uint8_t* user_context,
                        const size_t user_context_length,
                        const uint8_t* encrypted_message,
                        const size_t encrypted_message_length,
                        uint8_t* plain_message,
                        size_t* plain_message_length);

Token Protect mode

Additional encrypting data (auth_tag, iv, etc.) called "token" is generated by Themis and is stored in a buffer that is separate from the encrypted message (preserving the length of the original message). Error statusis returned if decryption is not possible.

/* encrypt */
themis_status_t themis_secure_cell_encrypt_token_protect(const uint8_t* master_key,
                              const size_t master_key_length,
                              const uint8_t* user_context,
                              const size_t user_context_length,
                              const uint8_t* message,
                              const size_t message_length,
                              uint8_t* token,
                              size_t* token_length,
                              uint8_t* encrypted_message,
                              size_t* encrypted_message_length);   

/* decrypt */
themis_status_t themis_secure_cell_decrypt_token_protect(const uint8_t* master_key,
                              const size_t master_key_length,
                              const uint8_t* user_context,
                              const size_t user_context_length,
                              const uint8_t* encrypted_message,
                              const size_t encrypted_message_length,
                              const uint8_t* token,
                              const size_t token_length,
                              uint8_t* plain_message,
                              size_t* plain_message_length);

Context Imprint mode

All the necessary additional encryption data is derived from the user's context data. This mode doesn't calculate auth tag. This means that integrity checks can't be done, so if decryption is not possible, corrupted data is returned and no error is raised.

/* encrypt */
themis_status_t themis_secure_cell_encrypt_context_imprint(const uint8_t* master_key,
                              const size_t master_key_length,
                              const uint8_t* message,
                              const size_t message_length,
                              const uint8_t* context,
                              const size_t context_length,
                              uint8_t* encrypted_message,
                              size_t* encrypted_message_length);

/* decrypt */
themis_status_t themis_secure_cell_decrypt_context_imprint(const uint8_t* master_key,
                              const size_t master_key_length,
                              const uint8_t* encrypted_message,
                              const size_t encrypted_message_length,
                              const uint8_t* context,
                              const size_t context_length,
                              uint8_t* plain_message,
                              size_t* plain_message_length);