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.

Quickstart

Requirements

In addition to the common set of build tools, Themis currently requires either the OpenSSL or LibreSSL package with the developer version of the package (as it provides header files). In either case, we strongly recommend you using the most recent version of these packages.

Installing Themis

Get Themis source code from GitHub

git clone https://github.com/cossacklabs/themis.git

Note that the default installation assumes the use of the standard LibreSSL/OpenSSL library and will install Themis to the standard /usr/lib and /usr/include locations.

In a typical case, all you need to do is (depending on your rights, sudo might be necessary):

make install

If your path is different, please see Building and installing for more details on how to change it.

To build and run tests, you can use:

make test

All the tests need to be passed with no errors.

Building Themis for C++

To install ThemisPP, type:

make themispp_install

To build and run exactly Themispp suit, you can:

make themispp_test test_spp

Using ThemisPP in your project

Insert

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

into your code.

Examples

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 they 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 {
     void gen();
     const std::vector<uint8_t>& get_priv();
     const std::vector<uint8_t>& get_pub();  
};

Description:

  • alg_t_p - template argument that sets the algorithm to be used. Value themispp::EC for Elliptic Curve or themispp::RSA for RSA
  • gen() - Generates key pair.
  • get_priv() - Returns generated private key.
  • 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. It is secure enough for exchanging 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 into 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).

You can read more about Secure Message's cryptographic internals.

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. Throws themispp::exception on failure.
  • const std::vector<uint8_t>& encrypt(const std::vector<uint8_t>& data) - encrypt message. Returns encrypted Secure Message container as binary vector. Throws themispp::exception on failure.
  • const std::vector<uint8_t>& decrypt(const std::vector<uint8_t>& data) - decrypt encrypted Secure Message container passed as binary vector. Returns decrypted data. Throws themispp::exception on failure.
  • const std::vector<uint8_t>& sign(const std::vector<uint8_t>& data) - Sign message with private_key. Returns signed Secure Message container as binary vector. Throws themispp::exception on failure.
  • const std::vector<uint8_t>& verify(const std::vector<uint8_t>& data) - Verify binary vector contained signed secure message container with public_key. Return verified data. Throws themispp::exception on failure.
Example

Initialise encrypter:

themispp::secure_message_t b(private_key,peer_public_key);

Encrypt message:

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

Decrypt message:

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

Sign message:

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

Verify message:

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

Secure Cell

The Secure Сell functions provide the means of protection for arbitrary data contained in stores, such as 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 filename) 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 using the authentication data is to validate 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 your application. The Secure Cell functions offer variants that address this issue in different ways.

The encryption algorithm used by Secure Cell (by default) is AES-256. The length of the generated authentication data is 16 bytes.

Secure Cell is available in 3 modes:

  • Seal mode: the mode that is the most secure and easy to use. This is your best choice most of the time.
  • Token protect mode: the mode that is the most secure and easy to use. Also your best choice most of the time.
  • Context imprint mode: length-preserving version of Secure Cell with no additional data stored. Should be used carefully.

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

Secure Cell Seal mode
Secure Cell 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>& context);
    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);
};

Description:

  • secure_cell_seal_t(const std::vector<uint8_t>& password) - initialise scell seal mode object with password.

  • const std::vector<uint8_t>& encrypt(const std::vector<uint8_t>& data, const std::vector<uint8_t>& context) - encrypt message with context. Return encrypted message. Throws themispp::exception on failure.

  • const std::vector<uint8_t>& encrypt(const std::vector<uint8_t>& data) - encrypt message without context. Return encrypted message. Throws themispp::exception on failure.

  • const std::vector<uint8_t>& encrypt(const std::vector<uint8_t>& data, const std::vector<uint8_t>& context) - decrypt message with context. Return decrypted message. Throws themispp::exception on failure.

  • const std::vector<uint8_t>& decrypt(const std::vector<uint8_t>& data) - decrypt message without context. Return decrypted message. Throws themispp::exception on failure.

Example

Initialise encrypter/decrypter

themispp::secure_cell_seal_t sm(password);

Encrypt

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

Decrypt

try {
     std::vector<uint8_t> decrypted_message = sm.decrypt(message, context);
   } catch(themispp::exception& 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>& context);
    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>& get_token();
    void set_token(const std::vector<uint8_t>& token);
};

Description: - secure_cell_token_protect_t(const std::vector<uint8_t>& password) - initialise scell token-protect mode object with password. - const std::vector<uint8_t>& encrypt(const std::vector<uint8_t>& data, const std::vector<uint8_t>& context) - encrypt message with context. Return encrypted message. Throws themispp::exception on failure. - const std::vector<uint8_t>& encrypt(const std::vector<uint8_t>& data) - encrypt message without context. Return encrypted message. Throws themispp::exception on failure. - const std::vector<uint8_t>& encrypt(const std::vector<uint8_t>& data, const std::vector<uint8_t>& context) - decrypt message with context. Return decrypted message. Throws themispp::exception on failure. - const std::vector<uint8_t>& decrypt(const std::vector<uint8_t>& data) - decrypt message without context. Return decrypted message. Throws themispp::exception on failure. - const std::vector<uint8_t>& get_token() - return token after encryption. - void set_token(const std::vector<uint8_t>& token) - set token before decryption;

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(themispp::exception& e) {
     e.what();
   }

Decrypt

try {
      sm.set_token(token);  
      std::vector<uint8_t> decrypted_message = sm.decrypt(message, context);
   } catch(themispp::exception& 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>& encrypt(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 scell context_imprint mode object with password. - const std::vector<uint8_t>& encrypt(const std::vector<uint8_t>& data, const std::vector<uint8_t>& context) - encrypt message with context. Return encrypted message. Throws themispp::exception on failure. - const std::vector<uint8_t>& encrypt(const std::vector<uint8_t>& data, const std::vector<uint8_t>& context) - decrypt message with context. Return decrypted message. Throws themispp::exception on failure.

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(themispp::exception& e) {
     e.what();
   }

Decrypt

try {
     std::vector<uint8_t> decrypted_message = sm.decrypt(message, context);
   } catch(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 bound 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 the 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, secure_session_transport_interface_t* transport);
   const std::vector<uint8_t>& wrap(const std::vector<uint8_t>& data);
   const std::vector<uint8_t>& unwrap(const std::vector<uint8_t>& data);
   const std::vector<uint8_t>& init();
   bool is_established();
   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, secure_session_transport_interface_t* transport) - initialise Secure Session object with id, private_key and transport. Throws themispp::exception on failure.
  • const std::vector<uint8_t>& init() - return a Secure Session initialisation message. Throws themispp::exception on failure.
  • void connect() - create and send to peer a Secure Session initialisation message. Return nothing. Throws themispp::exception on failure.
  • const std::vector<uint8_t>& wrap(const std::vector<uint8_t>& data) - return wrapped message. Throws themispp::exception on failure.
  • void send(const std::vector<uint8_t>& data) - create wrapped message and send it to peer. Return nothing. Throws themispp::exception on failure.
  • const std::vector<uint8_t>& unwrap(const std::vector<uint8_t>& data) - return unwrapped message. If is_established() call return false the unwrapped message must be send to peer without any corrections. Throws themispp::exception on failure.
  • const std::vector<uint8_t>& receive() - return received from peer and decrypted plain message. Throws themispp::exception 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

Initialise callbacks class

class transport: public themispp::secure_session_callback_interface_t {
   transport(...) {
     // initialise trusted public keys storage
     // init communication channel with peer
 }
 virtual const data_t& get_pub_key_by_id(const data_t& id) {
     // retrieve public key for peer user_id from trusted storage (file, db etc.)
     return public_key;
 }
 virtual void send(const data_t& data) {
     //send message to peer
 }
 virtual const data_t& receive() {
     // wait and receive buffer_length bytes from peer 
     return accepted message;
 }
};
Secure Session client

First, initialise session:

transport t;
themispp::secure_session_t session(client_id, client_private_key, &t); // t - created transport callback object

// this call say that it is client
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=ssession.receive();
Secure Session server

First, initialise session:

transport t;
themispp::secure_session_t session(server_id, server_private_key, &t); // t - created transport callback object

// there is not connect() method call - it is server.

while !(session.is_established()):
   session.receive();

Sending/receiving message works in a manner similar to the client side.

Secure Session wrap/unwrap

Initialise callbacks class:

class pub_key_storage: public themispp::secure_session_callback_interface_t {
   transport(...) {
     // initialise trusted public keys storage
 }
 virtual const data_t& get_pub_key_by_id(const data_t& id) {
     //retrieve public key for peer user_id from trusted storage (file, db etc.
     return public_key;
 }
};
Secure Session client

First, initialisation:

 pub_key_storage t;
 themispp::secure_session_t session(client_id, client_private_key, &t); //t - created transport callback object
 std::vector<uint8_t> message=session.init();
do {
   // send message to server
   // receive answer_message from server
   message=session.unwrap(answer_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 encrypted_message to peer by any preffered method

To decrypt the received message, use:

//receive encrypted_message from peer 
std::vector<uint8_t> message = session.unwrap(encrypted_message);
Secure Session server

First, initialise everything:

 pub_key_storage t;
 themispp::secure_session_t session(client_id, client_private_key, &t); //t - created transport callback object
 // there is not connection_request method call - it is a server

 while(!session.is_established()) {
     // receive message from server
     std::vector<uint8_t> answer_message=session.unwrap(message);
     // send answer_message to server
}

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 data_t& shared_secret);
   const data_t& init();
   const data_t& proceed(const std::vector<uint8_t>& data);
   const bool get();
};

Description: - secure_comparator_t(const data_t& shared_secret) - initialise secure comparator object with a shared_secret. - init() - return a Secure Comparator initialisation message. - proceed(const std::vector<uint8_t>& data) - proceed with current and create the next comparation messages (when necessary). - get() - return true if result of comparison matches, false if doesn't.

Secure Comparator workflow

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

Secure Comparator client and server
std::string shared_secret("shared_secret");

themispp::secure_comparator_t client(STR_2_VEC(shared_secret));
themispp::secure_comparator_t server(STR_2_VEC(shared_secret));

std::vector<uint8_t> buf;

// this defines client call
buf = client.init();

// checks in a loop, imagine sending `buf` from client to server and back
buf = server.proceed(buf);
buf = client.proceed(buf);
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.