Using Themis in C++

Introduction

The Themispp extension provides access to the features and functions of the Themis cryptographic library in C++:

  • Key generation: the creation of public/private key pairs, used in Secure Message and Secure Session.
  • Secure Message: the secure exchange of messages between two parties. RSA + PSS + PKCS#7 or ECC + ECDSA (based on key choice), AES GCM container.
  • Secure Storage (aka Secure Cell): provides secure storage of record based data through symmetric encryption and data authentication. AES GCM / AES CTR containers.
  • Secure Session: the establishment of a session between two peers, within which the data can be securely exchanged with higher security guarantees. EC + ECDH, AES container.

You can learn more about the Themis library in the general documentation.

Supported versions

C++03, C++11, C++14 are tested and supported.

Quickstart

Installing stable version from packages

ThemisPP is a header-only wrapper around the core Themis library. In order to use the wrapper you still need to have the core library installed.

  1. Install Themis Core as a system library using your system's package manager.

⚠️ IMPORTANT: ThemisPP requires core Themis headers to be installed, therefore you need to install the development package: libthemis-dev for Debian and Ubuntu, libthemis-devel for RHEL and CentOS.

  1. Download source code of the latest stable Themis Core from GitHub.

  2. Copy ThemisPP header file directory src/wrappers/themis/themispp into your project.

Building latest version from source

If the stable package version does not suit your needs, you can manually build and install the latest version of Themis from source code.

  1. Build and install Themis Core library into your system.
  2. Build and install ThemisPP wrapper from source code.

Importing Themis

Add relevant headers to your code:

#include <themispp/secure_cell.hpp>
#include <themispp/secure_comparator.hpp>
#include <themispp/secure_keygen.hpp>
#include <themispp/secure_message.hpp>
#include <themispp/secure_session.hpp>

and you're good to go!

Examples

Some code samples for Themis objects are available in docs/examples/c++ folder and described in tests/themispp folder.

Using Themis

Keypair generation

Themis supports both Elliptic Curve and RSA algorithms for asymmetric cryptography. Algorithm type is chosen according to the generated key type. Asymmetric keys are needed for Secure Message and Secure Session objects.

⚠️ WARNING: When you distribute private keys to your users, make sure the keys are sufficiently protected. You can find the guidelines here.

NOTE: When using public keys of other peers, make sure they come from trusted sources.

Keypair generation interface

enum asym_algs { EC, RSA };

template <asym_algs alg_t_p>
class themispp::secure_key_pair_generator_t
{
     secure_key_pair_generator_t();

     void gen();
     const std::vector<uint8_t>& get_priv();
     const std::vector<uint8_t>& get_pub();  
};

Description:

  • template <asym_algs alg_t_p>
    Template argument that sets the algorithm to be used:
  • themispp::EC for Elliptic Curve
  • themispp::RSA for RSA

  • secure_key_pair_generator_t<asym_algs>()
    A random key pair is generated on construction.

  • void gen()
    Generate a new key pair explicitly.

  • const std::vector<uint8_t>& get_priv()
    Returns generated private key.

  • const std::vector<uint8_t>& get_pub()
    Returns generated public key.

Example

// or themispp::secure_key_pair_generator_t<themispp::RSA> for RSA
themispp::secure_key_pair_generator_t<themispp::EC> g;

std::vector<uint8_t> private_key = g.get_priv();
std::vector<uint8_t> pub_key = g.get_pub();

Secure Message

The Secure Message functions provide a sequence-independent, stateless, contextless messaging system. This may be preferred in cases that don't require frequent sequential message exchange and/or in low-bandwidth contexts. This is secure enough to exchange messages from time to time, but if you'd like to have Perfect Forward Secrecy and higher security guarantees, please consider using Secure Session instead.

The Secure Message functions offer two modes of operation:

In Sign/Verify mode, the message is signed using the sender's private key and is verified by the receiver using the sender's public key. The message is packed in a suitable container and ECDSA is used by default to sign the message (when RSA key is used, RSA+PSS+PKCS#7 digital signature is used).

In Encrypt/Decrypt mode, the message will be encrypted with a randomly generated key (in RSA) or a key derived by ECDH (in ECDSA), via symmetric algorithm with Secure Cell in seal mode (keys are 256 bits long).

The mode is selected by using appropriate methods. The sender uses wrap and unwrap methods for encrypt/decrypt mode. A valid public key of the receiver and a private key of the sender are required in this mode. For sign/verify mode sign and verify methods should be used. They only require a private key for signing and a public key for verification respectively.

Read more about the Secure Message's cryptographic internals here.

Secure Message interface

class themispp::secure_message_t
{
     secure_message_t(const std::vector<uint8_t>& private_key,
                      const std::vector<uint8_t>& peer_public_key);

     const std::vector<uint8_t>& encrypt(const std::vector<uint8_t>& data);
     const std::vector<uint8_t>& decrypt(const std::vector<uint8_t>& data);
     const std::vector<uint8_t>& sign(const std::vector<uint8_t>& data);
     const std::vector<uint8_t>& verify(const std::vector<uint8_t>& data);
};

Description:

  • secure_message_t(const std::vector<uint8_t>& private_key, const std::vector<uint8_t>& peer_public_key)
    Initialise Secure Message object with private_key and peer_public_key (possibly empty).
    Throws themispp::exception_t on failure.

  • const std::vector<uint8_t>& encrypt(const std::vector<uint8_t>& data)
    Encrypt data, return encrypted message container.
    Requires both private_key and peer_public_key to be set.
    Throws themispp::exception_t on failure.

  • const std::vector<uint8_t>& decrypt(const std::vector<uint8_t>& data)
    Decrypt encrypted data, return decrypted data.
    Requires both private_key and peer_public_key to be set.
    Throws themispp::exception_t on failure.

  • const std::vector<uint8_t>& sign(const std::vector<uint8_t>& data)
    Sign data with private key, return signed message container.
    Requires private_key to be set.
    Throws themispp::exception_t on failure.

  • const std::vector<uint8_t>& verify(const std::vector<uint8_t>& data)
    Verify signed data with public key, return verified data.
    Requires peer_public_key to be set. Peer public key should be from the same keypair as private key.
    Throws themispp::exception_t on failure.

All methods provide additional overloads that accept pairs of iterators instead of vector references.

Example

Initialise encrypter:

themispp::secure_message_t b(private_key, peer_public_key);

Encrypt message:

try {
    std::vector<uint8_t> encrypted_message = b.encrypt(plaintext_message);
}
catch (const themispp::exception_t& e) {
    e.what();
}

Decrypt message:

try {
    std::vector<uint8_t> decrypted_message = b.decrypt(encrypted_message);
}
catch (const themispp::exception_t& e) {
    e.what();
}

Sign message:

try {
    std::vector<uint8_t> signed_message = b.sign(plaintext_message);
}
catch (const themispp::exception_t& e) {
    e.what();
}

Verify message:

try {
    std::vector<uint8_t> verified_message = b.verify(signed_message);
}
catch (const themispp::exception_t& e) {
    e.what();
}

Secure Cell

The Secure Сell functions provide the means of protection for arbitrary data contained in stores, i.e. database records or filesystem files. These functions provide both strong symmetric encryption and data authentication mechanisms.

The general approach is that given: - input: some source data to protect, - key: a password, - context: plus an optional "context information",

Secure Cell functions will produce: - cell: the encrypted data, - authentication tag: some authentication data.

The purpose of the optional "context information" (i.e. a database row number or file name) is to establish a secure association between this context and the protected data. In short, even when the password is known, if the context is incorrect, the decryption will fail.

The purpose of the authentication data is to verify that given a correct password (and context), the decrypted data is indeed the same as the original source data.

The authentication data must be stored somewhere. The most convenient way is to simply append it to the encrypted data, but this is not always possible due to the storage architecture of an application. The Secure Cell functions offer different variants that address this issue.

By default, the Secure Cell uses the AES-256 encryption algorithm. The generated authentication data is 16 bytes long.

Secure Cell is available in 3 modes:

  • Seal mode: the most secure and user-friendly mode. Your best choice most of the time.
  • Token protect mode: the most secure and user-friendly mode. Your best choice most of the time.
  • Context imprint mode: length-preserving version of Secure Cell with no additional data stored. Should be used with care and caution.

You can learn more about the underlying considerations, limitations, and features here.

Secure Cell Seal Mode

Seal mode interface
class themispp::secure_cell_seal_t
{
    secure_cell_seal_t(const std::vector<uint8_t>& password);

    const std::vector<uint8_t>& encrypt(const std::vector<uint8_t>& data);
    const std::vector<uint8_t>& encrypt(const std::vector<uint8_t>& data,
                                        const std::vector<uint8_t>& context);

    const std::vector<uint8_t>& decrypt(const std::vector<uint8_t>& data);
    const std::vector<uint8_t>& decrypt(const std::vector<uint8_t>& data,
                                        const std::vector<uint8_t>& context);
};

Description:

  • secure_cell_seal_t(const std::vector<uint8_t>& password)
    Construct Secure Cell in seal mode with password (must be non-empty). Throws themispp::exception_t on failure.

  • const std::vector<uint8_t>& encrypt(const std::vector<uint8_t>& data, const std::vector<uint8_t>& context)
    Encrypt data with additional context, return encrypted message.
    Throws themispp::exception_t on failure.

  • const std::vector<uint8_t>& encrypt(const std::vector<uint8_t>& data)
    Encrypt data without context, return encrypted message.
    Throws themispp::exception_t on failure.

  • const std::vector<uint8_t>& decrypt(const std::vector<uint8_t>& data, const std::vector<uint8_t>& context)
    Decrypt data with additional context, return decrypted message.
    Throws themispp::exception_t on failure.

  • const std::vector<uint8_t>& decrypt(const std::vector<uint8_t>& data)
    Decrypt data without context, return decrypted message.
    Throws themispp::exception_t on failure.

All methods provide additional overloads that accept pairs of iterators instead of vector references.

Example

Initialise encrypter/decrypter:

themispp::secure_cell_seal_t sm(password);

Encrypt (with context):

try {
    std::vector<uint8_t> encrypted_message = sm.encrypt(message, context);
}
catch (const themispp::exception_t& e) {
    e.what();
}

Decrypt (without context):

try {
    std::vector<uint8_t> decrypted_message = sm.decrypt(message);
}
catch (const themispp::exception_t& e) {
    e.what();
}

Secure Cell Token-Protect Mode

Token-Protect mode interface
class themispp::secure_cell_token_protect_t {
    secure_cell_token_protect_t(const std::vector<uint8_t>& password);

    const std::vector<uint8_t>& encrypt(const std::vector<uint8_t>& data);
    const std::vector<uint8_t>& encrypt(const std::vector<uint8_t>& data,
                                        const std::vector<uint8_t>& context);
    const std::vector<uint8_t>& get_token();

    void set_token(const std::vector<uint8_t>& token);
    const std::vector<uint8_t>& decrypt(const std::vector<uint8_t>& data);
    const std::vector<uint8_t>& decrypt(const std::vector<uint8_t>& data,
                                        const std::vector<uint8_t>& context);
};

Description:

  • secure_cell_token_protect_t(const std::vector<uint8_t>& password)
    Initialise Secure Cell in token-protect mode with password (must be non-empty).
    Throws themispp::exception_t on failure.

  • const std::vector<uint8_t>& encrypt(const std::vector<uint8_t>& data, const std::vector<uint8_t>& context)
    Encrypt data with additional context, return encrypted message.
    Throws themispp::exception_t on failure.

  • const std::vector<uint8_t>& encrypt(const std::vector<uint8_t>& data)
    Encrypt data without context, return encrypted message.
    Throws themispp::exception_t on failure.

  • const std::vector<uint8_t>& get_token()
    Get authentication token after encryption.

  • void set_token(const std::vector<uint8_t>& token)
    Set authentication token before decryption.

  • const std::vector<uint8_t>& encrypt(const std::vector<uint8_t>& data, const std::vector<uint8_t>& context)
    Decrypt data with additional context, return decrypted message.
    Throws themispp::exception_t on failure.

  • const std::vector<uint8_t>& decrypt(const std::vector<uint8_t>& data)
    Decrypt data without context, return decrypted message.
    Throws themispp::exception_t on failure.

All methods provide additional overloads that accept pairs of iterators instead of vector references.

Example

Initialise encrypter/decrypter:

themispp::secure_cell_token_protect_t sm(password);

Encrypt:

try {
    std::vector<uint8_t> encrypted_message = sm.encrypt(message, context);
    std::vector<uint8_t> token = sm.get_token();    
}
catch (const themispp::exception_t& e) {
    e.what();
}

Decrypt:

try {
    sm.set_token(token);    
    std::vector<uint8_t> decrypted_message = sm.decrypt(encrypted_message, context);
}
catch (const themispp::exception_t& e) {
    e.what();
}

Secure Cell Context-Imprint Mode

Context-Imprint mode interface
class themispp::secure_cell_context_imprint_t {
    secure_cell_context_imprint_t(const std::vector<uint8_t>& password);

    const std::vector<uint8_t>& encrypt(const std::vector<uint8_t>& data,
                                        const std::vector<uint8_t>& context);
    const std::vector<uint8_t>& decrypt(const std::vector<uint8_t>& data,
                                        const std::vector<uint8_t>& context);
};

Description:

  • secure_cell_context_imprint_t(const std::vector<uint8_t>& password)
    Initialise Secure Cell in context-imprint mode with password (must be non-empty).
    Throws themispp::exception_t on failure.

  • const std::vector<uint8_t>& encrypt(const std::vector<uint8_t>& data, const std::vector<uint8_t>& context)
    Encrypt data with additional context, return encrypted message.
    Throws themispp::exception_t on failure.

  • const std::vector<uint8_t>& encrypt(const std::vector<uint8_t>& data, const std::vector<uint8_t>& context)
    Decrypt data with additional context, return decrypted message.
    Throws themispp::exception_t on failure.

All methods provide additional overloads that accept pairs of iterators instead of vector references.

Note that context-imprint mode always requires a non-empty context.

Example

Initialise encrypter/decrypter:

themispp::secure_cell_context_imprint_t sm(password);

Encrypt:

try {
    std::vector<uint8_t> encrypted_message = sm.encrypt(message, context);
}
catch (const themispp::exception_t& e) {
    e.what();
}

Decrypt:

try {
    std::vector<uint8_t> decrypted_message = sm.decrypt(message, context);
}
catch (const themispp::exception& e) {
    e.what();
}

Secure Session

Secure Session is a sequence- and session- dependent, stateful messaging system. It is suitable for protecting long-lived peer-to-peer message exchanges where the secure data exchange is tied to a specific session context.

Secure Session operates in two stages: session negotiation where the keys are established and cryptographic material is exchanged to generate ephemeral keys and data exchange where exchanging of messages can be carried out between peers.

You can read a more detailed description of the process here.

Put simply, Secure Session takes the following form:

  • Both clients and server construct a Secure Session object, providing:
    • an arbitrary identifier,
    • a private key, and
    • a callback function that enables it to acquire the public key of the peers with which they may establish communication.
  • A client will generate a "connect request" and by whatever means it will dispatch that to the server.
  • A server will enter a negotiation phase in response to a client's "connect request".
  • Clients and servers will exchange messages until a "connection" is established.
  • Once a connection is established, clients and servers may exchange secure messages according to whatever application level protocol was chosen.

Secure Session interface

class themispp::secure_session_t {
    secure_session_t(const std::vector<uint8_t>& id,
                     const std::vector<uint8_t>& private_key,
                     std::shared_ptr<themispp::secure_session_callback_interface_t> transport);

    bool is_established() const;

    const std::vector<uint8_t>& init();
    const std::vector<uint8_t>& wrap(const std::vector<uint8_t>& data);
    const std::vector<uint8_t>& unwrap(const std::vector<uint8_t>& data);

    void connect();
    const std::vector<uint8_t>& receive();
    void send(const std::vector<uint8_t>& data);
};
  • secure_session_t(const std::vector<uint8_t>& id, const std::vector<uint8_t>& private_key, std::shared_ptr<themispp::secure_session_transport_interface_t> transport)
    Initialise Secure Session with (non-empty) peer id, private_key, and transport callbacks.
    Throws themispp::exception_t on failure.

  • bool is_established() const
    Checks if the session has been established and ready to use.

  • const std::vector<uint8_t>& init()
    Return a Secure Session initialisation message, send it to peer.
    Throws themispp::exception_t on failure.

  • const std::vector<uint8_t>& wrap(const std::vector<uint8_t>& data)
    Encrypt data, return wrapped message that can be sent to peer.
    Throws themispp::exception_t on failure.

  • const std::vector<uint8_t>& unwrap(const std::vector<uint8_t>& data)
    Decrypt data, return unwrapped message.
    If is_established() returns false then send result to peer without modifications. Otherwise, the decrypted message is returned.
    Throws themispp::exception_t on failure.

  • void connect()
    Create and send a Secure Session initialisation message to peer.
    Throws themispp::exception_t on failure.

  • void send(const std::vector<uint8_t>& data)
    Encrypt data and send it to peer.
    Throws themispp::exception_t on failure.

  • const std::vector<uint8_t>& receive()
    Receive a message from the peer, decrypt, and return it.
    If is_established() is false then proceed with connection, returns empty message.
    Throws themispp::exception_t on failure.

Secure Session Workflow

Secure Session can be used in two ways: - send/receive - when communication flow is fully controlled by the Secure Session object. - wrap/unwrap - when communication is controlled by the user.

Secure Session has two parties that are called client and server for the sake of simplicity, but they could be more precisely called initiator and acceptor - the only difference between them is in who starts the communication.

Secure Session relies on the user's passing a number of callback functions to send/receive messages - and the keys are retrieved from local storage (see more in Secure Session cryptosystem description).

Send/Receive mode

Implement and initialise callbacks:

class transport: public themispp::secure_session_callback_interface_t {
public:
    transport(...)
    {
        // Initialize trusted public keys storage.
        // Open communication channel with peer.
    }

    std::vector<uint8_t> get_pub_key_by_id(const std::vector<uint8_t>& id) override
    {
        // Retrieve public key for peer "id" from trusted storage (file, DB, etc.)
        // Return empty vector if there is no associated key.
        return public_key;
    }

    void send(const std::vector<uint8_t>& data) override
    {
        // Send "data" to peer.
    }

    const std::vector<uint8_t>& receive() override
    {
        // Receive a message for peer and store it.
        // Return a reference to the transport object field.
        return this->received_message;
    }
};
Secure Session client

First, initialise the session:

auto t = std::make_shared<transport>(...);
themispp::secure_session_t session(client_id, client_private_key, t);

// Client initiates connection.
session.connect();

while (!session.is_established()) {
    session.receive();
}

After the loop finishes, Secure Session is established and is ready to be used.

To send a message over established session use:

session.send(message);

To receive the message from session:

std::vector<uint8_t> message = session.receive();
Secure Session server

First, initialise the session:

auto t = std::make_shared<transport>(...);
themispp::secure_session_t session(server_id, server_private_key, t);

// There is no connect() call for server, just wait for the client.
while (!session.is_established()) {
    session.receive();
}

Sending/receiving messages is exactly the same as for the client.

Wrap/Unwrap mode

Implement and initialise callbacks. You need to implement only one of them:

class pub_key_storage: public themispp::secure_session_callback_interface_t
{
public:
    pub_key_storage(...)
    {
        // Initialize trusted public keys storage.
    }

    std::vector<uint8_t> get_pub_key_by_id(const std::vector<uint8_t>& id) override
    {
        // Retrieve public key for peer "id" from trusted storage (file, DB, etc.)
        // Return empty vector if there is no associated key.
        return public_key;
    }
};
Secure Session client

First, initialisation:

auto t = std::make_shared<pub_key_storage>(...);
themispp::secure_session_t session(client_id, client_private_key, t);

// The client initiates connection.
std::vector<uint8_t> message = session.init();
do {
   send_to_server(message);
   message = receive_from_server();
   message = session.unwrap(message);
} while (!session.is_established());

After the loop finishes, Secure Session is established and is ready to be used.

To encrypt the outgoing message use:

std::vector<uint8_t> encrypted_message = session.wrap(message);
send_to_server(encrypted_message);

To decrypt the received message use:

std::vector<uint8_t> encrypted_message = receive_from_server(); 
std::vector<uint8_t> message = session.unwrap(encrypted_message);
Secure Session server

First, initialise everything:

auto t = std::make_shared<pub_key_storage>(...);
themispp::secure_session_t session(server_id, server_private_key, t);

// The server simply waits for the client to arrive.
while (!session.is_established()) {
     std::vector<uint8_t> request = receive_from_client();
     std::vector<uint8_t> reply = session.unwrap(request);
     send_to_client(reply);
}

Secure Session is ready. Send/receive works in the same way as the client's example above. See the full example available in docs/examples/c++.

Secure Comparator

Secure Comparator is an interactive protocol for two parties that compares whether they share the same secret or not. It is built around a Zero Knowledge Proof-based protocol (Socialist Millionaire's Protocol), with a number of security enhancements.

Secure Comparator is transport-agnostic and only requires the user(s) to pass messages in a certain sequence. The protocol itself is ingrained into the functions and requires minimal integration efforts from the developer.

Secure Comparator interface

class secure_comparator_t
{
   secure_comparator_t(const std::vector<uint8_t>& shared_secret);

   const std::vector<uint8_t>& init();
   const std::vector<uint8_t>& proceed(const std::vector<uint8_t>& data);

   bool get() const;
};

Description:

  • secure_comparator_t(const std::vector<uint8_t>& shared_secret)
    Initialise Secure Comparator with a shared_secret to compare.
    Throws themispp::exception_t on failure.

  • const std::vector<uint8_t>& init()
    Start comparison and return an initialisation message to send.
    Throws themispp::exception_t on failure.

  • const std::vector<uint8_t>& proceed(const std::vector<uint8_t>& data)
    Process data and return the next message to send (empty if comparison is complete).
    Throws themispp::exception_t on failure.

  • bool get() const
    Return true if compared data matches, false otherwise.

Secure Comparator workflow

Secure Comparator has two parties — called "client" and "server" — the only difference between them is in who starts the comparison.

Example

std::string secret_string("shared_secret");
std::vector<uint8_t> secret_bytes(secret_string.begin(), secret_string.end());

themispp::secure_comparator_t client(secret_bytes);
themispp::secure_comparator_t server(secret_bytes);

// Think of this shared buffer as the network channel.
std::vector<uint8_t> buf;

// The client initiates the comparison.
buf = client.init();

while (!buf.empty()) {
    buf = server.proceed(buf);
    buf = client.proceed(buf);
}

bool result_client = client.get();
bool result_server = server.get();

After the loop finishes, the comparison is over and its result can be checked by calling comparator.get().

That's it! See the full example available in docs/examples/cpp.