Themis Tutorials

About Themis tutorials

This is a page containing all the materials marked as "Tutorials" for Themis. It was created for the purposes of an easy whole-text search through all the tutorials at once for those who find it convenient. However, it's recommended that you see the Tutorials and How-tos section on the main Themis page or make use of the "Search" form on top of each product page (Themis, Acra, Hermes) on the main Cossack Labs Documentation page.

This section combines all the tutorials available for Themis wrappers. The available languages are:

Using Themis with Java / Android

Install on Android

Themis wrapper for Android is available via Maven. Just add the following lines to your build.gradle file:

repositories {
    google()
    jcenter()
    // ...
    maven { url "https://dl.bintray.com/cossacklabs/maven/" }
}

dependencies {
    // ...
    implementation 'com.cossacklabs.com:themis:0.11.0'
}

Alternatively, you may wish to build Android package from source. In this case, remember that 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.

Install on Desktop Java

Installing Themis on desktop Java is a two-step process:

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

Importing Themis

After you have installed the Themis wrapper, import its packages in your Java files:

import com.cossacklabs.themis.*;

Examples and tests for Android

  • Android test cases (in the tests directory) are simple self-describing easy-to-understand examples of our APIs usage scenarios. Feel free to explore them.

  • Themis Java examples for Android and Desktop Java illustrate how to link library to the project and how to use it. It covers Secure Cell, Secure Message and Secure Session, and works with Themis Interactive Simulator.

  • You can also read our blog post Building Secure Chat server, which includes Android client, implementation of Secure Session, and Secure Cell.

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 necessary 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

You can easily generate keypairs for Secure Message and Secure Session the following way:

Keypair pair = KeypairGenerator.generateKeypair();
PrivateKey privateKey = pair.getPrivateKey();
PublicKey publicKey = pair.getPublicKey();

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.

Sending many messages to the same recipient

1. Create a Secure Message object with your PrivateKey and recipient's PublicKey:

SecureMessage encryptor = new SecureMessage(yourPrivateKey, peerPublicKey);

2. Encrypt each outgoing message:

byte[] encryptedMessage = encryptor.wrap(messageToSend);

3. Decrypt each incoming message:

byte[] receivedMessage = encryptor.unwrap(wrappedMessage);
Sending messages to many recipients

1. Create Secure Message object with your PrivateKey:

SecureMessage encryptor = new SecureMessage(yourPrivateKey);

2. Encrypt each outgoing message specifying recipients' PublicKey:

byte[] encryptedMessage = encryptor.wrap(messageToSend, peerPublicKey);

3. Decrypt each incoming message specifying the sender's PublicKey:

byte[] receivedMessage = encryptor.unwrap(wrappedMessage, peerPublicKey);
Signing messages

1. Create Secure Message object with your PrivateKey:

SecureMessage signer = new SecureMessage(yourPrivateKey);

2. Sign one or more messages:

byte[] signedMessage = signer.sign(message);
Verifying the signed messages

1. Create Secure Message object with your PublicKey. Remember to use PublicKey from the same keypair as Private key you used for signing message.

SecureMessage verifier = new SecureMessage(yourPublicKey);

2. Verify the messages received from your peer:

try {
    byte[] verifiedMessage = verifier.verify(signedMessage);
} catch (SecureMessageWrapException e) {
    // invalid signature or other error occurred
}

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.

Initialising Secure Cell

Create Secure Cell using key as a String or as a Byte Array.

SecureCell cell = new SecureCell(yourSecretByteArrayKey);

// or
SecureCell cell = new SecureCell("your secret password string");

NOTE: When unspecified, Secure Cell will use SecureCell.MODE_SEAL by default. Read more about the Secure Cell modes and which mode to choose here.

Secure Cell Seal Mode

Initialise cell:

SecureCell cell = new SecureCell("your secret password string", SecureCell.MODE_SEAL);

Encrypt:

// context is optional
SecureCellData cellData = cell.protect(context, data);

The result of the function call is SecureCellData object, which is a simple container for protected data. You may get the actual protected data:

byte[] protectedData = cellData.getProtectedData();

Decrypt:
The context should be the same as in the protect function call for successful decryption.

// context is optional
byte[] data = cell.unprotect(context, cellData);
Secure Cell Token-protect Mode

Initialise cell:

SecureCell cell = new SecureCell("your secret password string", SecureCell.MODE_TOKEN_PROTECT);

Encrypt:

// context is optional
SecureCellData cellData = cell.protect(context, data);

In this mode, the result holds additional data (opaque to the user, but necessary for successful decryption):

byte[] protectedData = cellData.getProtectedData();

if (cellData.hasAdditionalData()) {
    byte[] additionalData = cellData.getAdditionalData();
}

Decrypt:

// context is optional
byte[] data = cell.unprotect(context, cellData);
Secure Cell Context-Imprint Mode

Initialise cell:

SecureCell cell = new SecureCell("your secret password string", SecureCell.MODE_CONTEXT_IMPRINT);

Encrypt:

// context required
SecureCellData cellData = cell.protect(context, data);

byte[] protectedData = cellData.getProtectedData();

Decrypt:

NOTE: For successful decryption, the context should be the same as in the protect function call.

// context is required
byte[] data = cell.unprotect(context, cellData);

You can also use one object to encrypt different data with different keys:

SecureCellData cellData1 = cell.protect(key1, context1, data1);
...
SecureCellData cellData2 = cell.protect(key2, context2, data2);
...

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 Workflow

Secure Session has two parties 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).

Secure Sockets

If your application already uses Java sockets for communication, you can easily add/increase security by replacing them with our SecureSocket and SecureServerSocket.

1. Implement ISessionCallbacks interface:

  • getPublicKeyForId will return the peer's trusted public key when it is needed by the system.

  • stateChanged is just a notification callback. You may use it for informational purpose, to update your UI, or just to create a dummy (do-nothing) implementation.

Example using anonymous class:

ISessionCallbacks callbacks = new ISessionCallbacks() {
    @Override
    public PublicKey getPublicKeyForId(SecureSession session, byte[] id) {
        // get trusted PublicKey of user id
        PublicKey publicKey = getUserPublicKeyFromDatabaseOrOtherStorageOrSource(id);
        return publicKey; // or null if key is not found
    }

    @Override
    public void stateChanged(SecureSession session) {
       // update UI: for example, draw a nice padlock signaling to the user that his/her communication is now secured
    }
}

2. Replace all of your sockets with our secure versions:

On client:

// Socket clientSocket = new Socket(...);
Socket clientSocket = new SecureSocket(..., clientId, clientPrivateKey, callbacks);

On server:

// ServerSocket serverSocket = new ServerSocket(...);
ServerSocket serverSocket = new SecureServerSocket(..., serverId, serverPrivateKey, callbacks);

3. Enjoy!

Basic Secure Session

This API is useful when your application has an already established network processing path and this path is doing more than just use sockets. In this case, you would just want to add some function calls that wrap/unwrap the outgoing/incoming data buffers.

1. Similarly to the case with Secure Sockets — implement ISessionCallbacks interface:

ISessionCallbacks callbacks = new ISessionCallbacks() {
    @Override
    public PublicKey getPublicKeyForId(SecureSession session, byte[] id) {
        // get trusted PublicKey of user id
        PublicKey publicKey = getUserPublicKeyFromDatabaseOrOtherStorageOrSource(id);
        return publicKey; // or null if key is not found
    }

    @Override
    public void stateChanged(SecureSession session) {
       // update UI: for example, draw a nice padlock indicating to the user that his/her communication is now secured
    }
}

2. Create a SecureSession object:

SecureSession session = new SecureSession(yourId, yourPrivateKey, callbacks);

3. On the client side, initiate the Secure Session negotiation by generating and sending a connection request:

byte[] connectRequest = session.generateConnectRequest();
// send connectRequest to the server

4. Start receiving and parsing incoming data on both sides:

// receive some data and store it in receiveBuffer
SecureSession.UnwrapResult result = session.unwrap(receiveBuffer);

switch (result.getDataType()) {
    case USER_DATA:
        // this is the actual data that was encrypted by your peer using SecureSession.wrap
        byte[] data = result.getData();
        // process the data according to your application's flow for incoming data
        break;
    case PROTOCOL_DATA:
        // this is the internal Secure Session protocol data. An opaque response was generated, just send it to your peer
        byte[] data = result.getData();
        // send the data to your peer as is
        break;
    case NO_DATA:
        // this is the internal Secure Session protocol data, but no response is needed (this usually takes place on the client side when protocol negotiation completes)
        // do nothing
        break;
}

5. When the protocol negotiation is completed, you may send the encrypted data to your peer:

byte[] wrappedData = session.wrap(yourData);
// send wrappedData to your peer
Secure Session with transport callbacks

This API is useful when you want to clearly decouple the security from the network communication in your application:

1. Implement ITransportSessionCallbacks interface. This interface extends ISessionCallbacks interface, which means you have to implement two additional functions:

ITransportSessionCallbacks callbacks = new ITransportSessionCallbacks() {
    // implement getPublicKeyForId and stateChanged as in basic ISessionCallbacks
    ...

    @Override
    public void write(byte[] buffer) {
        // it will be called when Secure Session needs to send something to your peer
        // just send buffer to your peer
    }

    @Override
    public byte[] read() {
        // here you should issue a read request to your underlying transport (for example, read data from socket or pipe)
        // return the buffer with read data
    }
}

2. Create a SecureTransportSession object:

SecureTransportSession session = new SecureTransportSession(yourId, yourPrivateKey, callbacks);

3. On the client side, initiate the Secure Session negotiation by sending a connection request:

session.connect();

4. When the negotiation is complete, you may send/receive the data on both sides:

// sending data
session.write(dataToSend);

...

// receiving data (probably, through a receive loop)
byte[] receivedData = session.read();

That's it!

Please see the tests and Mobile WebSocket Example to get a more complete understanding of how Secure Session works.

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 workflow

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

Secure Comparator client
byte[] compareData = ... // shared secret to compare
SecureCompare client = new SecureCompare(compareData);

// Initiating secure compare (client, step1)
byte[] peerData = client.begin();

while (client.getResult() == SecureCompare.CompareResult.NOT_READY) {
    // send data on server and receive response
    sendDataOnServer(peerData);
    peerData = receiveFromServer();

    // proceed and send again
    peerData = client.proceed(peerData);
}

After the loop ends, the comparison is over and its result can be checked by calling getResult:

if (client.getResult() == SecureCompare.CompareResult.MATCH) {
    // secrets match
} else {
    // secrets don't match
}
Secure Comparator server

The server part can be described in any language, but let's pretend here that both client and server are using Java:

byte[] compareData = // shared secret to compare
SecureCompare server = new SecureCompare(compareData);

// Initiating secure compare (client, step1)
byte[] peerData = new byte[0];

while (server.getResult() == SecureCompare.CompareResult.NOT_READY) {
    // receive from client
    peerData = receiveFromClient();

    // proceed and send again
    peerData = server.proceed(peerData);
    sendDataOnClient(peerData);
}

After the loop finishes, the comparison is over and its result can be checked by calling getResult:

if (server.getResult() == SecureCompare.CompareResult.MATCH) {
    // secrets match
} else {
    // secrets don't match
}

Using Themis with Swift (iOS/OSX)

Introduction

The Swift Themis library provides access to the features and functions of the Themis cryptographic library:

  • Key generation: creation of public/private key pairs, used in Secure Message and Secure Session.
  • Secure Message: 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: a session between two peers, within which the data can be securely exchanged with higher security guarantees. EC + ECDH, AES container.
  • Secure Comparator: comparison of a secret between two parties without leaking anything related to the secret: Zero-Knowledge Proof-based authentication system. Hardened Socialist Millionaire Protocol + ed25519.

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

Now using Themis in Swift is easy!

Supported versions

objcthemis has Objective-C code and Swift port so it works in ObjC and Swift projects. We tested it on Swift versions 4.0 - 5.0.

Quickstart

Installing stable version from CocoaPods

Themis wrapper for iOS and macOS is available on CocoaPods. Just add the following lines to your Podfile:

source 'https://github.com/cossacklabs/Podspecs.git'
pod 'themis'

and run

pod install

to install Themis.

Using BoringSSL

By default, Themis uses OpenSSL as crypto-engine.

If your project uses BoringSSL or gRPC libraries, you might want to switch to BoringSSL crypto-engine for Themis, too (available since 0.10.1). Currently, BoringSSL is only available via CocoaPods:

pod 'themis/themis-boringssl'

BoringSSL for iOS currently doesn't support bitcode, which means that Themis that uses BoringSSL also doesn't support bitcode :(

Installing stable version from Carthage

Themis wrapper for iOS and macOS is also available via Carthage. Just add the following line to your Cartfile:

github "cossacklabs/themis"

and run

carthage update

to install Themis.

Examples for Swift

Using Themis

In order to use Themis, you need to import it first:

import 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 necessary 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

To generate a pair of keys, Themis has the TSKeyGen class. Its init method has one parameter: the algorithm type (Elliptic Curve (EC) or RSA algorithms are supported).

// .EC or .RSA can be used to select the key pair type
guard let keyGeneratorEC = TSKeyGen(algorithm: .EC) else {
    print("failed to generate keys")
    return
}
let privateKeyEC = keyGeneratorEC.privateKey as Data
let publicKeyEC = keyGeneratorEC.publicKey as Data

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.

Encryption

To encrypt a message use client private key and server public key and convert them to NSData:

// base64-encoded keys
let serverPublicKeyString = "VUVDMgAAAC2ELbj5Aue5xjiJWW3P2KNrBX+HkaeJAb+Z4MrK0cWZlAfpBUql"
let clientPrivateKeyString = "UkVDMgAAAC13PCVZAKOczZXUpvkhsC+xvwWnv3CLmlG0Wzy8ZBMnT+2yx/dg"

guard
    let serverPublicKey = Data(base64Encoded: serverPublicKeyString),
    let clientPrivateKey = Data(base64Encoded: clientPrivateKeyString)
else {
    print("failed to decode base64")
    return
}

Initialise encrypter:

let encrypter = TSMessage(inEncryptModeWithPrivateKey: clientPrivateKey,
                          peerPublicKey: serverPublicKey)!

Encrypt message:

let message = "- Knock, knock.\n- Who’s there?\n*very long pause...*\n- Java."

do {
    let encryptedMessage = try encrypter.wrap(message.data(using: .utf8))
    print("encrypted message: \(encryptedMessage)")
}
catch let error as NSError {
    print("failed to encrypt message: \(error)")
    return
}

Result (the encryption result for the same data chunk is different every time and can't be used as a test):

encrypted message: <20270426 53000000 00010140 0c000000 10000000 1f000000 ad443c21 d6d7df98 a101e48b b3757b04 c5710e04 5720b3c2 fe674f54 73e10ad4 ee722d3e 42244b6d c5099ac4 89dfda90 75fae62a aa733872 c8180d>
Decryption

Use the server private key and the client public key for decryption:

// base64 encoded keys
let serverPrivateKeyString = "UkVDMgAAAC1FsVa6AMGljYqtNWQ+7r4RjXTabLZxZ/14EXmi6ec2e1vrCmyR"
let clientPublicKeyString = "VUVDMgAAAC1SsL32Axjosnf2XXUwm/4WxPlZauQ+v+0eOOjpwMN/EO+Huh5d"

guard
    let serverPrivateKey = Data(base64Encoded: serverPrivateKeyString),
    let clientPublicKey = Data(base64Encoded: clientPublicKeyString)
else {
    print("failed to decode base64")
    return
}

Initialise decrypter:

let decrypter = TSMessage(inEncryptModeWithPrivateKey: serverPrivateKey,
                          peerPublicKey: clientPublicKey)!

Decrypt message:

do {
    let decryptedMessage = try decrypter.unwrapData(encryptedMessage)
    print("decrypted message: \(decryptedMessage)")
}
catch let error as NSError {
    print("failed to decrypt message: \(error)")
    return
}

Result:

decrypted message: - Knock, knock.\n- Who’s there?\n*very long pause...*\n- Java.!
Sign / Verify

The only code difference between sign/verify and encrypt/decrypt mode is in the initialiser:

let signer = TSMessage(inSignVerifyModeWithPrivateKey: privateKey,
                       peerPublicKey: nil)!

let verifier = TSMessage(inSignVerifyModeWithPrivateKey: nil,
                         peerPublicKey: publicKey)!

Use private key for signing message and public key from the same keypair for verifying message.

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.

Initialising Secure Cell

In order to initialise Secure Cell object, you will need to provide master key as NSData:

let masterKeyString = "UkVDMgAAAC13PCVZAKOczZXUpvkhsC+xvwWnv3CLmlG0Wzy8ZBMnT+2yx/dg"
let masterKeyData = Data(base64Encoded: masterKeyString)!
Secure Cell Seal Mode

Initialise encrypter/decrypter:

guard let cellSeal = TSCellSeal(key: masterKeyData) else {
    print("failed to initialize seal mode")
    return
}

Encryption:

let message = "all your base are belong to us"
let context = "for the great justice"

do {
    // "context" is an optional parameter and may be omitted
    let encryptedMessage = try cellSeal.wrap(message.data(using: .utf8)!,
                                             context: context.data(using: .utf8)!)
    print("encrypted message: \(encryptedMessage)")
}
catch let error as NSError {
    print("failed to encrypt message: \(error)")
    return
}

Decryption:

do {
    let decryptedMessage = try cellSeal.unwrapData(encryptedMessage,
                                                   context: context.data(using: .utf8)!)
    print("decrypted message: ", String(data: decryptedMessage, encoding: .utf8)!)  
}
catch let error as NSError {
    print("failed to decrypt message: \(error)")
    return
}
Secure Cell Token-Protect Mode

Initialise encrypter/decrypter:

guard let cellToken = TSCellToken(key: masterKeyData) else {
    print("failed to initialize token-protect mode")
    return
}

Encryption:

let message = "Roses are grey. Violets are grey."
let context = "I'm a dog"

var encryptedMessage: TSCellTokenEncryptedData = TSCellTokenEncryptedData()
do {
    // "context" is an optional parameter and may be omitted
    let encryptedMessage = try cellToken.wrap(message.data(using: .utf8)!,
                                              context: context.data(using: .utf8)!)
    print("encrypted message: \(encryptedMessage.cipherText)")
    print("authentication token: \(encryptedMessage.token)")
}
catch let error as NSError {
    print("failed to encrypt: \(error)")
    return
}

Decryption:

do {
    let decryptedMessage = try cellToken.unwrapData(encryptedMessage,
                                                    context: context.data(using: .utf8)!)
    print("decrypted message: ", String(data: decryptedMessage, encoding: .utf8)!)  
}
catch let error as NSError {
    print("failed to decrypt message: \(error)")
    return
}
Secure Cell Context-Imprint Mode

Initialise encrypter/decrypter:

guard let contextImprint = TSCellContextImprint(key: masterKeyData) else {
    print("failed to initialize context-imprint mode")
    return
}

Encryption:

let message = "Roses are red. My name is Dave. This poem have no sense"
let context = "Microwave"

do {
    // "context" is a REQUIRED parameter here
    let encryptedMessage = try contextImprint.wrap(message.data(using: .utf8)!,
                                                   context: context.data(using: .utf8)!)
    print("encrypted message: \(encryptedMessage)")
}
catch let error as NSError {
    print("failed to encrypt message: \(error)")
    return
}

Decryption:

do {
    let decryptedMessage = try contextImprint.unwrapData(encryptedMessage,
                                                         context: context.data(using: .utf8)!)
    print("decrypted message: ", String(data: decryptedMessage, encoding: .utf8)!)
}
catch let error as NSError {
    print("failed to decrypt message: \(error)")
    return
}

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 Workflow

Secure Session has two parties 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).

Initialise Secure Session

Client ID can be obtained from the server or is generated by the client. You can play with Themis Interactive Simulator to get the keys and simulate whole client-server communication.

guard
    let clientIdData = clientId.data(using: String.Encoding.utf8),
    let clientPrivateKey = Data(base64Encoded: kClientPrivateKey)
else {
    print("failed to decode base64")
    return
}

var transport = Transport()
transport.setupKeys(kServerId, serverPublicKey: kServerPublicKey)

let session = TSSession(userId: clientIdData, privateKey: clientPrivateKey, callbacks: transport)
Transport interface

Implement transport interface to return server's public key. Transport layer simply returns server public key for requests using server ID.

final class Transport: TSSessionTransportInterface {

    fileprivate var serverId: String?
    fileprivate var serverPublicKeyData: Data?

    func setupKeys(_ serverId: String, serverPublicKey: String) {
        self.serverId = serverId
        self.serverPublicKeyData = Data(base64Encoded: serverPublicKey,
                                        options: .ignoreUnknownCharacters)
    }

    override func publicKey(for binaryId: Data!) throws -> Data {
        let error: Error = NSError(domain: "com.themisserver.example", code: -1, userInfo: nil)
        let stringFromData = String(data: binaryId, encoding: String.Encoding.utf8)
        if stringFromData == nil {
            throw error
        }

        if stringFromData == serverId {
            guard let resultData: Data = serverPublicKeyData else {
                throw error
            }
            return resultData
        }

        return Data()
    }
}
Connect Secure Session
do {
    guard let connectionMessage = try session!.connectRequest() else {
        throw NSError(domain: "com.themisserver.example", code: -2, userInfo: nil)
    }
    // send "connectionMessage" to server
}
catch let error {
    print("failed to generate connection request: \(error)")
    return
}

Client should send connectionMessage, get response, and check if isSessionEstablished before sending payload.

let data: Data = ... // received server response data after sending "connectionMessage"

do {
    guard let decryptedMessage = try session!.unwrapData(data) else {
        throw NSError(domain: "com.themisserver.example", code: -4, userInfo: nil)
    }
    if session!.isSessionEstablished() {
        // session is established: break out of loop
    } else {
        // session is NOT established yet
        // send "decryptedMessage" to the server
    }
}
catch let error {
    print("failed to negotiate: \(error)")
    return
}

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

Send and receive data
do {
    guard let encryptedMessage = try session?.wrap(message.data(using: String.Encoding.utf8)) else {
        throw NSError(domain: "com.themisserver.example", code: -5, userInfo: nil)
    }
    // send "encryptedMessage" to the server
} catch let error {
    print("failed to encrypt: \(error)")
    return
}

// ...

do {
    guard let decryptedMessage = try session!.unwrapData(data) else {
        throw NSError(domain: "com.themisserver.example", code: -6, userInfo: nil)
    }
    // "decryptedMessage" contains server response
}
catch let error {
    print("failed to decrypt: \(error)")
    return
}

This is it. See the full example in docs/examples/Themis-server/swift.

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 workflow

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

Secure Comparator client
let sharedMessage = "shared secret"

let client = TSComparator(messageToCompare: sharedMessage.data(using: .utf8)!)!

var data = try? client.beginCompare()

while (client.status() == TSComparatorStateType.comparatorNotReady) {
    // send data to the server, receive response, and process it
    self.sendDataOnServer(data)
    data = self.receiveServerResponse()
    data = try? client.proceedCompare(data)
}

After the loop finishes, the comparison is over and its result can be checked by calling status:

if (client.status() == TSComparatorStateType.comparatorMatch) {
    // secrets match
    print("SecureComparator secrets match")
} else {
    // secrets don't match
    print("SecureComparator secrets do not match")
}
Secure Comparator server

The server part can be described in any language, but let's pretend here that both client and server are using Swift.

let sharedMessage = "shared secret"
let server = TSComparator(messageToCompare: sharedMessage.data(using: .utf8)!)!

var data: Data

while (server.status() == TSComparatorStateType.comparatorNotReady) {
    // receive from client, process, and send reply
    data = self.receiveFromClient()
    data = try? server.proceedCompare(data)
    self.sendToClient(data)
}

After the loop finishes, the comparison is over and its result can be checked by calling status:

if (server.status() == TSComparatorStateType.comparatorMatch) {
    // secrets match
    print("SecureComparator secrets match")
} else {
    // secrets don't match
    print("SecureComparator secrets do not match")
}

Using Themis with Objective-C (iOS/macOS)

Introduction

The objc-themis library provides access to the features and functions of the Themis cryptographic library:

  • Key generation: creation of public/private key pairs, used in Secure Message and Secure Session.
  • Secure Message: 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: a session between two peers, within which the data can be securely exchanged with higher security guarantees. EC + ECDH, AES container.
  • Secure Comparator: comparison of a secret between two parties without leaking anything related to the secret: Zero-Knowledge Proof-based authentication system. Hardened Socialist Millionaire Protocol + ed25519.

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

Installing stable version from CocoaPods

Themis wrapper for iOS and macOS is available on CocoaPods. Just add the following lines to your Podfile:

source 'https://github.com/cossacklabs/Podspecs.git'
pod 'themis'

and run

pod install

to install Themis.

Using BoringSSL

By default, Themis uses OpenSSL as crypto-engine.

If your project uses BoringSSL or gRPC libraries, you might want to switch to BoringSSL crypto-engine for Themis, too (available since 0.10.1). Currently, BoringSSL is only available via CocoaPods:

pod 'themis/themis-boringssl'

Currently, BoringSSL for iOS doesn't support bitcode, so Themis that uses BoringSSL also doesn't support bitcode :(

Installing stable version from Carthage

Themis wrapper for iOS and macOS is also available via Carthage. Just add the following line to your Cartfile:

github "cossacklabs/themis"

and run

carthage update

to install Themis.

Examples for Objective-C

Using Themis

In order to use Themis, you need to import it first.

The header file is a bit different with each dependency manager. For CocoaPods use the following header:

#import <objcthemis/objcthemis.h>

If you install Themis via Carthage, please use this one:

#import <themis/themis.h>

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 necessary 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

To generate a pair of keys, Themis has a class TSKeyGen. Its init method has one parameter: the algorithm type (Elliptic Curve (EC) or RSA algorithms are supported).

/*TSKeyGenAsymmetricAlgorithmEC or TSKeyGenAsymmetricAlgorithmRSA can be used*/
TSKeyGen *keygenEC = [[TSKeyGen alloc] initWithAlgorithm:TSKeyGenAsymmetricAlgorithmEC];

if (!keygenEC) {
    NSLog(@"failed to generate keys");
    return;
}

NSData *privateKey = keygenEC.privateKey;
NSData *publicKey = keygenEC.publicKey;

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.

Encryption

To encrypt a message, use client private key and server public key, and convert them to NSData:

// base64 encoded keys
NSString *serverPublicKeyString = @"VUVDMgAAAC2ELbj5Aue5xjiJWW3P2KNrBX+HkaeJAb+Z4MrK0cWZlAfpBUql";
NSString *clientPrivateKeyString = @"UkVDMgAAAC13PCVZAKOczZXUpvkhsC+xvwWnv3CLmlG0Wzy8ZBMnT+2yx/dg";

NSData *serverPublicKey =
    [[NSData alloc] initWithBase64EncodedString:serverPublicKeyString 
                                        options:NSDataBase64DecodingIgnoreUnknownCharacters];
NSData *clientPrivateKey =
    [[NSData alloc] initWithBase64EncodedString:clientPrivateKeyString 
                                        options:NSDataBase64DecodingIgnoreUnknownCharacters];

Initialise encrypter:

TSMessage *encrypter = [[TSMessage alloc] initInEncryptModeWithPrivateKey:clientPrivateKey 
                                                            peerPublicKey:serverPublicKey];

Encrypt message:

NSString *message = @"All your base are belong to us!";

NSError *themisError;
NSData *encryptedMessage = [encrypter wrapData:[message dataUsingEncoding:NSUTF8StringEncoding]
                                         error:&themisError];
if (themisError) {
    NSLog(@"failed to encrypt: %@", themisError);
    return;
}
NSLog(@"encrypted message: %@", encryptedMessage);

Result (the encryption result on the same data chunk is different every time and can't be used as a test):

encrypted message: <20270426 53000000 00010140 0c000000 10000000 1f000000 ad443c21 d6d7df98 a101e48b b3757b04 c5710e04 5720b3c2 fe674f54 73e10ad4 ee722d3e 42244b6d c5099ac4 89dfda90 75fae62a aa733872 c8180d>
Decryption

Use server private key and client public key for decryption:

// base64 encoded keys
NSString *serverPrivateKeyString = @"UkVDMgAAAC1FsVa6AMGljYqtNWQ+7r4RjXTabLZxZ/14EXmi6ec2e1vrCmyR";
NSString *clientPublicKeyString = @"VUVDMgAAAC1SsL32Axjosnf2XXUwm/4WxPlZauQ+v+0eOOjpwMN/EO+Huh5d";

NSData *serverPrivateKey =
    [[NSData alloc] initWithBase64EncodedString:serverPrivateKeyString
                                        options:NSDataBase64DecodingIgnoreUnknownCharacters];
NSData *clientPublicKey =
    [[NSData alloc] initWithBase64EncodedString:clientPublicKeyString
                                        options:NSDataBase64DecodingIgnoreUnknownCharacters];

Initialise decrypter:

TSMessage *decrypter = [[TSMessage alloc] initInEncryptModeWithPrivateKey:serverPrivateKey
                                                            peerPublicKey:clientPublicKey];

Decrypt message:

NSError *themisError;
NSData *decryptedMessage = [decrypter unwrapData:encryptedMessage
                                           error:&themisError];
if (themisError) {
    NSLog(@"failed to decrypt: %@", themisError);
    return;
}

NSString *resultString = [[NSString alloc] initWithData:decryptedMessage
                                               encoding:NSUTF8StringEncoding];
NSLog(@"decrypted message: %@", resultString);

Result:

decrypted message: All your base are belong to us!
Sign / Verify

The only code difference from encrypt/decrypt mode is the initialiser methods:

TSMessage *signer = [[TSMessage alloc] initInSignVerifyModeWithPrivateKey:privateKey
                                                            peerPublicKey:nil];

TSMessage *verifier = [[TSMessage alloc] initInSignVerifyModeWithPrivateKey:nil
                                                              peerPublicKey:publicKey];

Use private key for signing message and public key from the same keypair for verifying message.

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.

Initialising Secure Cell

In order to initialise Secure Cell object, you will need to provide master key as NSData:

NSString *masterKeyString = @"UkVDMgAAAC13PCVZAKOczZXUpvkhsC+xvwWnv3CLmlG0Wzy8ZBMnT+2yx/dg";
NSData *masterKeyData =
    [[NSData alloc] initWithBase64EncodedString:masterKeyString
                                        options:NSDataBase64DecodingIgnoreUnknownCharacters];
Secure Cell Seal Mode

Initialise encrypter/decrypter:

TSCellSeal *cellSeal = [[TSCellSeal alloc] initWithKey:masterKeyData];

Encrypt:

NSString *message = @"All your base are belong to us!";
NSString *context = @"For great justice";

// "context" is an optional parameter and may be omitted
NSError *themisError;
NSData *encryptedMessage = [cellSeal wrapData:[message dataUsingEncoding:NSUTF8StringEncoding]
                                      context:[context dataUsingEncoding:NSUTF8StringEncoding]
                                        error:&themisError];
if (themisError) {
    NSLog(@"failed to encrypt: %@", themisError);
    return;
}
NSLog(@"encrypted message: %@", encryptedMessage);

Decrypt:

NSError *themisError;
NSData *decryptedMessage = [cellSeal unwrapData:encryptedMessage
                                        context:[context dataUsingEncoding:NSUTF8StringEncoding]
                                          error:&themisError];
if (themisError) {
    NSLog(@"failed to decrypt: %@", themisError);
    return;
}

NSString *resultString = [[NSString alloc] initWithData:decryptedMessage
                                               encoding:NSUTF8StringEncoding];
NSLog(@"decrypted message: %@", resultString);

Secure Cell Token-Protect Mode

Initialise encrypter/decrypter:

TSCellToken *cellToken = [[TSCellToken alloc] initWithKey:masterKeyData];

Encryption:

NSString *message = @"Roses are grey. Violets are grey.";
NSString *context = @"I'm a dog";

// "context" is an optional parameter and may be omitted
NSError *themisError;
TSCellTokenEncryptedData *encryptedMessage =
    [cellToken wrapData:[message dataUsingEncoding:NSUTF8StringEncoding]
                context:[context dataUsingEncoding:NSUTF8StringEncoding]
                  error:&themisError];

if (themisError) {
    NSLog(@"failed to encrypt: %@", themisError);
    return;
}
NSLog(@"encrypted message: %@", encryptedMessage.cipherText);
NSLog(@"authentication token: %@", encryptedMessage.token);

Decryption:

NSError *themisError;
NSData *decryptedMessage = [cellToken unwrapData:encryptedMessage
                                         context:[context dataUsingEncoding:NSUTF8StringEncoding] 
                                           error:&themisError];
if (themisError) {
    NSLog(@"failed to decrypt: %@", themisError);
    return;
}

NSString *resultString = [[NSString alloc] initWithData:decryptedMessage
                                               encoding:NSUTF8StringEncoding];
NSLog(@"decrypted message: %@", resultString);
Secure Cell Context-Imprint Mode

Initialise encrypter/decrypter:

TSCellContextImprint *contextImprint = [[TSCellContextImprint alloc] initWithKey:masterKeyData];

Encryption:

NSString *message = @"Roses are red. My name is Dave. This poem have no sense";
NSString *context = @"Microwave";

// "context" is a REQUIRED parameter for context-imprint mode
NSError *themisError;
NSData *encryptedMessage = [contextImprint wrapData:[message dataUsingEncoding:NSUTF8StringEncoding]
                                            context:[context dataUsingEncoding:NSUTF8StringEncoding]
                                              error:&themisError];
if (themisError) {
    NSLog(@"failed to encrypt: %@", themisError);
    return;
}

NSLog(@"encrypted message: %@", encryptedMessage);

Decryption:

NSError *themisError;
NSData *decryptedMessage = [contextImprint unwrapData:encryptedMessage
                                              context:[context dataUsingEncoding:NSUTF8StringEncoding]
                                                error:&themisError];
if (themisError) {
    NSLog(@"failed to decrypt: %@", themisError);
    return;
}

NSString *resultString = [[NSString alloc] initWithData:decryptedMessage
                                               encoding:NSUTF8StringEncoding];
NSLog(@"decrypted message: %@", resultString);

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 workflow

Secure Session has two parts 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).

Initialise Secure Session

Client ID can be obtained from the server or generated by the client. You can play with Themis Interactive Simulator to get the keys and simulate whole client-server communication.

NSString *userId = [UIDevice currentDevice].name;

Transport *transport = [Transport new];
TSSession *session = [[TSSession alloc] initWithUserId:[userId dataUsingEncoding:NSUTF8StringEncoding]
                                            privateKey:privateKey
                                             callbacks:transport];
Transport interface

Implement transport interface to return server's public key. Transport layer simply returns server public key for requests using server ID.

NSString *const kServerKey = @"VUVDMgAAAC11WDPUAhLfH+nqSBHh+XGOJBHL/cCjbtasiLZEwpokhO5QTD6g";

@implementation Transport

- (NSData *)publicKeyFor:(NSData *)binaryId error:(NSError **)error {
    NSString *stringFromData = [[NSString alloc] initWithData:binaryId
                                                     encoding:NSUTF8StringEncoding];
    if ([stringFromData isEqualToString:@"server"]) {
        return [[NSData alloc] initWithBase64EncodedString:kServerKey
                                                   options:NSDataBase64DecodingIgnoreUnknownCharacters];
    }
    return nil;
}

@end
Connect Secure Session
NSError *error;
NSData *sessionEstablishingData = [session connectRequest:&error];
if (error) {
    NSLog(@"failed to generate connection request: %@", error);
    return;
}
// send "sessionEstablishingData" to the server

Client should send sessionEstablishingData, get response and check if isSessionEstablished before sending payload.

NSData *responseData = ... // received server response data after sending "sessionEstablishingData"

NSError *error;
NSData *unwrappedData = [session unwrapData:responseData error:&wrappingError];
if (error) {
    NSLog(@"failed to negotiate: %@", error);
    return;
}

if ([session isSessionEstablished]) {
    // session is established, break out of the loop
}

// session is NOT established yet
// send "unwrappedData" to the server again

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

Send and receive data
NSString *message = @"message to send";

NSError *error;
NSData *wrappedData = [session wrapData:[message dataUsingEncoding:NSUTF8StringEncoding]
                                  error:&error];
if (error) {
    NSLog(@"failed to encrypt: %@", error);
    return;
}

// send "wrappedData" using NSURLSession or any other kind of transport

...

NSData *receivedData = ...; // receive data from network here

NSError *error;
NSData *unwrappedMessage = [session unwrapData:receivedData error:&error];

if (error) {
    NSLog(@"failed to decrypt: %@", error);
    return;
}

NSString * receivedMessage = [[NSString alloc] initWithData:unwrappedMessage
                                                   encoding:NSUTF8StringEncoding];
// process "receivedMessage"

That's it!

Please refer to the docs/examples/Themis-server/Obj-C project to gain a full understanding and overview of how Secure Session works.

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 workflow

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

Secure Comparator client

NSString *sharedSecret = @"shared secret";
NSData *sharedSecretData = [sharedSecret dataUsingEncoding:NSUTF8StringEncoding];

TSComparator *client = [[TSComparator alloc] initWithMessageToCompare:sharedSecretData];

NSError *error;
NSData *data = [client beginCompare:&error];

while ([client status] == TSComparatorNotReady) {
    // send data to the server, receive response, and process it
    [self sendDataToServer:data];
    data = [self receiveResponseFromServer];
    data = [client proceedCompare:data error:&error];
}

After the loop finishes, the comparison is over and its result can be checked by calling status:

if ([client status] == TSComparatorMatch) {
    // secrets match
    NSLog(@"SecureComparator secrets match");
} else {
    // secrets don't match
    NSLog(@"SecureComparator secrets don't match");
}
Secure Comparator server

The server part can be described in any language, but let's pretend here that both client and server are using Objective-C.

NSString *sharedSecret = @"shared secret";
NSData *sharedSecretData = [sharedSecret dataUsingEncoding:NSUTF8StringEncoding];

TSComparator *server = [[TSComparator alloc] initWithMessageToCompare:sharedSecretData];

NSError *error;
while ([server status] == TSComparatorNotReady) {
    // receive from client, process, and send reply
    NSData *data = [self receiveFromClient];
    data = [server proceedCompare:data error:&error];
    [self sendDataToClient:data];
}

After the loop finishes, the comparison is over and its result can be checked by calling status:

if ([server status] == TSComparatorMatch) {
    // secrets match
    NSLog(@"SecureComparator secrets match");
} else {
    // secrets don't match
    NSLog(@"SecureComparator secrets do not match");
}

Using Themis in Python

Introduction

The pythemis extension (wrapper) provides access to the features and functions of the Themis cryptographic library in Python (2.7+ and 3.x):

  • Key generation: creation of public/private key pairs, used in Secure Message and Secure Session.
  • Secure Message: 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: a session between two peers, within which the data can be securely exchanged with higher security guarantees. EC + ECDH, AES container.
  • Secure Comparator: comparison of a secret between two parties without leaking anything related to the secret: Zero-Knowledge Proof-based authentication system. Hardened Socialist Millionaire Protocol + ed25519.

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

There are also example console utils available for the Python wrapper for Themis (as well as for some other wrappers — see the full list here). They help understand the specific mechanics of encryption/decryption processes of this specific wrapper. You can find the example console utils for the Python wrapper here.

Supported versions

pythemis is tested on various python versions – starting 2.7 to 3.5 and higher.

Quickstart

Installing stable version from pip

PyThemis wrapper is available on pip. 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.

2. Install PyThemis wrapper via pip:
bash pip install pythemis

NOTE: You'll need to use sudo if you're installing it system-wide, not just in a dedicated or virtual user environment.

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.

IMPORTANT: Remember that 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.

1. Build and install Themis Core library into your system.

2. Build and install PyThemis wrapper from source code.

Importing Themis

Add to your code:

import pythemis

Now you're good to go!

You can also import only a certain module (keypair generation, for example):

from pythemis import skeygen

Examples for Python

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 necessary 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
class KEY_PAIR_TYPE(object):
    EC = 'EC'
    RSA = 'RSA'
    CHOICES = (EC, RSA)


class GenerateKeyPair(object):
    def __init__(self, alg)
    def export_private_key(self) -> bytes
    def export_public_key(self) -> bytes

Description:

  • __init__(self, alg)
    Generates key pair. Parameter alg sets the algorithm to be used:
    • KEY_PAIR_TYPE.EC — for Elliptic Curve
    • KEY_PAIR_TYPE.RSA — for RSA
    Throws ThemisError on failure.
  • export_private_key(self)
    Return bytes with generated private key.
  • export_public_key(self)
    Return bytes with generated public key.
Example
from pythemis.skeygen import GenerateKeyPair, KEY_PAIR_TYPE

# or KEY_PAIR_TYPE.RSA for RSA
obj = GenerateKeyPair(KEY_PAIR_TYPE.EC)
private_key = obj.export_private_key()
public_key = obj.export_public_key()

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 SMessage(object):
   def __init__(self, private_key: bytes, peer_public_key: bytes)
   def wrap(self, message) -> bytes
   def unwrap(self, message) -> bytes

def ssign(private_key: bytes, message: bytes) -> bytes
def sverify(public_key: bytes, message: bytes) -> bytes

Description:

  • class SMessage — encrypted secure message
  • __init__(self, private_key: bytes, peer_public_key: bytes)
    Initialise Secure Message object with private_key and peer_public_key. Asymmetric keys are needed for Secure Message and Secure Session. Throws ThemisError on failure.
  • wrap(self, message: bytes) -> bytes
    Encrypt message. Return encrypted Secure Message container as binary string. Throws ThemisError on failure.
  • unwrap(self, message: bytes) -> bytes
    Decrypt encrypted Secure Message container passed as binary string (message). Return decrypted string. Throws ThemisError on failure.
  • signed Secure Message
  • ssign(private_key: bytes, message: bytes) -> bytes
    Sign message with private_key. Return signed Secure Message container as binary string. Throws ThemisError on failure.
  • sverify(public_key: bytes, message: bytes) -> bytes
    Verify binary vector contained signed secure message container (message) with public_key. Return binary string. Throws ThemisError on failure. Use public key from the same keypair as private key for verifying the message.
Example

Initialise encrypter:

from pythemis.skeygen import KEY_PAIR_TYPE, GenerateKeyPair
from pythemis.smessage import SMessage, ssign, sverify
from pythemis.exception import ThemisError

keypair1 = GenerateKeyPair(KEY_PAIR_TYPE.EC)
keypair2 = GenerateKeyPair(KEY_PAIR_TYPE.EC)

smessage = SMessage(keypair1.export_private_key(), keypair2.export_public_key())

Encrypt message:

try:
    encrypted_message = smessage.wrap(b'some message')
except ThemisError as e:
    print(e)

Decrypt message:

try:
    message = smessage.unwrap(encrypted_message)
except ThemisError as e:
    print(e)

Sign message:

For signing/verifying make sure that you use keys from the same keypair – private key for signing message and public key for verifying message.

try:
    signed_message = ssign(keypair1.export_private_key(), b'some message')
except ThemisError as e:
    print(e)

Verify message:

try:
    message = sverify(keypair1.export_public_key(), signed_message)
except ThemisError as e:
    print(e)

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 interface
class SCellSeal(object):
   def __init__(self, key: bytes)
   def encrypt(self, message: bytes, context=None: bytes) -> bytes
   def decrypt(self, message: bytes, context=None: bytes) -> bytes

Description:
- __init__(self, key: bytes)
Initialise Secure Cell in seal mode with key. - encrypt(self, message: bytes, context=None: bytes) -> bytes
Encrypt message with an optional context. Return encrypted message. Throws ThemisError on failure. - decrypt(self, message: bytes, context=None: bytes) -> bytes
Decrypt message with an optional context. Return plain message. Throws ThemisError on failure.

Example

Initialise encrypter/decrypter:

from pythemis.scell import SCellSeal
scell = SCellSeal(b'password')

Encrypt:

encrypted_message = scell.encrypt(b'message', b'context') 

Decrypt:

message = scell.decrypt(encrypted_message, b'context')  
Secure Cell Token-protect Mode
Token-protect mode interface
class SCellTokenProtect(object)
   def __init__(self, key: bytes)
   def encrypt(self, message: bytes, context=None: bytes) -> (bytes, bytes)
   def decrypt(self, message: bytes, additional_auth_data: bytes, context=None: bytes) -> bytes

Description: - __init__(self, key)
Initialise Secure Cell in token protect mode with key. - encrypt(self, message: bytes, context=None: bytes) -> (bytes, bytes)
Encrypt message with an optional context. Return two binary strings containing encrypted message and token. Throws ThemisError on failure. - decrypt(self, message: bytes, additional_auth_data: bytes, context=None: bytes) -> bytes
Decrypt message with additional_auth_data (aka token) and an optional context. Return plain message. Throws ThemisError on failure.

Example

Initialise encrypter/decrypter:

from pythemis.scell import SCellTokenProtect
scell = SCellTokenProtect(b'password')

Encrypt:

encrypted_message, additional_auth_data = scell.encrypt(b'message', b'some context') 

Decrypt:

message = scell.decrypt(encrypted_message, additional_auth_data, b'some context') 
Secure Cell Context-Imprint Mode
Context-imprint mode interface
class SCellContextImprint(object):
   def __init__(self, key: bytes)
   def encrypt(self, message: bytes, context: bytes) -> bytes
   def decrypt(self, message: bytes, context: bytes) -> bytes

Description: - __init__(self, key: bytes)
Initialise Secure Cell in context imprint mode with key. - encrypt(self, message: bytes, context: bytes) -> bytes
Encrypt message with context. Return encrypted message. Throws ThemisError on failure. - decrypt(self, message: bytes, context: bytes) -> bytes
Decrypt message with context. Return plain message. Throws ThemisError on failure.

Example

Initialise encrypter/decrypter:

from pythemis.scell import SCellContextImprint
scell = SCellContextImprint(b'some password')

Encrypt:

encrypted_message = scell.encrypt(b'test message', b'test context') 

Decrypt:

message = scell.decrypt(encrypted_message, b'test context') 

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 SSession(object):
   def __init__(self, id: bytes, private_key: bytes, transport: TransportStruct)
   def is_established(self) -> bool
   def connect_request(self) -> bytes
   def wrap(self, message: bytes) -> bytes
   def unwrap(self, message: bytes) -> bytes
   def connect(self)
   def send(self, message: bytes) -> int
   def receive(self) -> bytes

Description:

  • __init__(self, id: bytes, private_key: bytes, transport: TransportStruct)
    Initialie Secure Session object with id, private_key and transport. Throws ThemisError on failure.
  • is_established(self) -> bool
    Check whether Secure Session connection has been established. Throws ThemisError on failure.
  • connect(self)
    Create and send a Secure Session initialisation message to peer. Returns nothing. Throws ThemisError on failure.
  • send(self, message: bytes) -> int
    Encrypt message and send it to peer. Returns internal status code. Throws ThemisError on failure.
  • receive(self) -> bytes
    Receive a message from peer, decrypt and return it. Throws ThemisError on failure.
  • connect_request(self) -> bytes
    Return a Secure Session initialisation message. Throws ThemisError on failure.
  • wrap(self, message: bytes) -> bytes
    Encrypt message for peer. Throws ThemisError on failure.
  • unwrap(self, message: bytes) -> bytes
    Decrypt message from peer. Throws ThemisError on failure.
    Unwrapped message may contain either plaintext data or a control message. If is_control property is true, the message must be sent to peer as is.
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 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).

Communication flow is fully controlled by the Secure Session object
Transport class for send/receive

Implement all methods in the callbacks class:

class CustomTransport(object):
    def __init__(self, *args, **kwargs)
        # init communication channel with peer

    def send(self, message):
        # send message to peer

    def receive(self, buffer_length):
        # wait and receive at most buffer_length bytes from peer 
        return accepted_message

    def get_pub_key_by_id(self, user_id):
        # retrieve public key for peer user_id from trusted storage (file, db etc.)    
        return public_key
Secure Session client

First, initialise the session:

from pythemis.ssession import SSession
session = SSession(b'some client id', client_private_key, CustomTransport())
session.connect()
while not session.is_established():
    session.receive()

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

To send a message over the established session, use:

session.send(message)

To receive a message from the session:

message = ssession.receive()
Secure Session server

First, initialise the session:

session = SSession(b'some server id', server_private_key, CustomTransport())
# there is no need to call connect() method on the server side
while not session.is_established():
    session.receive()

Sending/receiving messages works in a manner similar to the client side after the connection is established.

To encrypt and send an outgoing message to the client use:

session.send(message)

To receive and decrypt a message from the client use:

message = session.receive()
Communication controlled by user
Transport class for wrap/unwrap

Implement only required methods in the callback class:

from pythemis.ssession import MemoryTransport


class CustomSimpleTransport(MemoryTransport):
   def __init__(self, *args, **kwargs)
       # initialize trusted public keys storage
       super(CustomSimpleTransport, self).__init__()       

   def get_pub_key_by_id(self, user_id):
       # retreive public key for peer user_id from trusted storage (file, db etc.)    
       return public_key
Secure Session client

First, the initialisation:

session = SSession(b'user_id2', client_private_key, CustomSimpleTransport())
# this call is only made by the client
encrypted_message = session.connect_request()

# send connect request to the peer
response_bytes = user_communication_send_method(encrypted_message)    
message = session.unwrap(response_bytes)

# establish the session
while not session.is_established():
    response_bytes = user_communication_send_method(message)    
    message = session.unwrap(response_bytes)

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

To encrypt the outgoing message, use:

encrypted_message = session.wrap(message)
# send encrypted_message to peer by any prefered method

To decrypt the received message, use:

# receive encrypted_message from peer 
message = session.unwrap(encrypted_message)
Secure Session server

First, initialise everything:

session = SSession(b'server_id_1', server_private_key, CustomSimpleTransport())
# there is no need to call connect() or connect_request() on the server side

encrypted_message = user_communication_recieve_method() 
message = session.unwrap(encrypted_message)

# NOTE: The condition is different for the server because we need to send
# the last piece of data to the client after establishing the session.
while message.is_control:
    # just return the unwrapped message to the user
    user_communication_send_method(message)

    encrypted_message = user_communication_recieve_method() 
    message = session.unwrap(encrypted_message)

Secure Session is ready.

Send/receive works in the same way as the client's example above.

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 SComparator:
   def __init__(self, shared_secret: bytes)
   def begin_compare(self) -> bytes
   def proceed_compare(self, message: bytes) -> bytes
   def is_compared(self) -> bool
   def is_equal(self) -> bool
   def result(self)

Description:
- __init__(self, shared_secret: bytes)
Initialise Secure Comparator object with a shared_secret. Throws ThemisError on failure. - begin_compare(self)
Return a Secure Comparator initialisation message. Throws ThemisError on failure. - proceed_compare(self, message)
Process message and create the next protocol message (when necessary). Throws ThemisError on failure. - is_compared(self) -> bool
Return True if comparison is finished. Throws ThemisError on failure. - is_equal(self) -> bool
Return True if comparison is finished and secrets are equal, otherwise return False. Throws ThemisError on failure. - result(self) -> int
Return the original comparison result code. Throws ThemisError on failure.

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
from pythemis.scomparator import SComparator


comparator = SComparator(b'shared_secret')

# this call is specific to the client
comparison_message = comparator.begin_compare()

while not comparator.is_compared():
    user_send_function(comparison_message)
    response = user_recieve_function()
    comparison_message = comparator.proceed_compare(response)

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

Secure Comparator server
from pythemis.scomparator import SComparator


comparator = SComparator(b'shared_secret')

while not comparator.is_compared():
     comparison_message = user_receive_function()
     comparison_message = comparator.proceed_compare(comparison_message)
     user_send_function(comparison_message)

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

Using Themis in PHP

Introduction

The phpthemis extension (wrapper) provides PHP access to the features and functions of the Themis cryptographic library:

  • Key generation: creation of public/private key pairs, used in Secure Message and Secure Session.
  • Secure Message: 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: a session between two peers, within which the data can be securely exchanged with higher security guarantees. EC + ECDH, AES container.

Currently, phpthemis extension (wrapper) doesn't support the:
- Secure Comparator: comparison of a secret between two parties without leaking anything related to the secret: Zero-Knowledge Proof-based authentication system. Hardened Socialist Millionaire Protocol + ed25519.
Secure Comparator is supported by other Themis wrappers and libraries on other languages.

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

Supported versions

PHP versions 5.6, 7.0, 7.1 and 7.2 are supported.

Quickstart

Installing stable version from packages

Themis extension for PHP is available via Cossack Labs package repository, just as the core library. You can install it using your system package manager.

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

2. Install PHP extension package.

Debian, Ubuntu: sudo apt-get install libphpthemis-php7.0 RHEL, CentOS: sudo yum install libphpthemis-php7.0

PHP extension is not available on macOS yet, you will have to build it from source code.

You can verify if PHPThemis is correctly installed by running:

php --ri phpthemis

Building the 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.

IMPORTANT: Remember that 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.

1. Build and install Themis Core library into your system.

2. Build and install PHPThemis wrapperfrom source code.

Examples and Tests for PHP

Some code samples for Themis objects are available in docs/examples/php folder.

We've also prepared tests in tests/phpthemis. Run them using following line:

./tests/phpthemis/run_tests.sh

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 necessary 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
EC

To generate an Elliptic Curve key pair:

mixed phpthemis_gen_ec_key_pair( void )

Return Values

Returns an associative array containing a public and private 256 bit EC keys on success. NULL is returned on error. The keys contained in the array values are strings containing binary data.

Example
$keys = phpthemis_gen_ec_key_pair();
if ($keys !== NULL) {
    $private_key = $keys['private_key'];
    $public_key  = $keys['public_key'];
}
RSA

To generate an RSA key pair:

mixed phpthemis_gen_rsa_key_pair( void )

Return Values

Returns an associative array containing public and private keys on success. NULL is returned on error. The keys contained in the array values are strings containing binary data.

Example
$keys = phpthemis_gen_rsa_key_pair();
if ($keys !== NULL) {
    $private_key = $keys['private_key'];
    $public_key  = $keys['public_key'];
}

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
mixed phpthemis_secure_message_wrap( string $senders_private_key,
                                     string $receivers_public_key,
                                     string $message )

mixed phpthemis_secure_message_unwrap( string $receivers_private_key,
                                       string $senders_public_key,
                                       string $secure_message )

Parameters:

phpthemis_secure_message_wrap is used for encryption/signing; returns encrypted or signed message, returns a string of binary data containing the encrypted or signed message on success. NULL is returned on error.

  • senders_private_key is the private (EC or RSA) key of the entity sending the message — we assume that the receiver has safely acquired the associated public key through other channels.

  • receivers_public_key is the public (EC or RSA) key of the entity receiving the message — we assume that the sender has safely acquired this key through other channels.

  • message is plaintext message.

phpthemis_secure_message_unwrap is used for decryption/verifying; returns a string containing the original message on success. NULL is returned on error.

  • receivers_private_key is the private (EC or RSA) key of the entity receiving the message.

  • senders_public_key is the public (EC or RSA) key of the entity sending the message - it is assumed that the receiver has safely acquired this key through other means.

  • secure_message is encrypted/signed message.

Example

Encrypt/Decrypt:

$sender_keys   = phpthemis_gen_ec_key_pair();
$receiver_keys = phpthemis_gen_ec_key_pair();
$message = 'The best laid schemes of mice and men go oft awry';
$smessage = phpthemis_secure_message_wrap($sender_keys['private_key'],
                                          $receiver_keys['public_key'],
                                          $message);
$rmessage = phpthemis_secure_message_unwrap($receiver_keys['private_key'],
                                            $sender_keys['public_key'],
                                            $message_to_send);
echo "Received : $rmessage\n";

Sign/Verify:

Use private key for signing message and public key from the same keypair for verifying the message.

$sender_keys   = phpthemis_gen_ec_key_pair();
$receiver_keys = phpthemis_gen_ec_key_pair();
$message = 'The best laid schemes of mice and men go oft awry';

// Passing NULL as receiver keys enables Sign/Verify mode
$smessage = phpthemis_secure_message_wrap($sender_keys['private_key'], NULL, $message);
$rmessage = phpthemis_secure_message_unwrap(NULL, $sender_keys['public_key'], $message_to_send);
echo "Received : $rmessage\n";

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 interface
Encryption
mixed phpthemis_scell_seal_encrypt( string $password, string $source_data [, string $context] )

Parameters:

  • password the password to be used for encryption.
  • source_data the data to be encrypted.
  • context the optional context (see above).

Return Values

Returns a string of binary data containing the encrypted data with the authentication data appended on success. NULL is returned on error.

Decryption
mixed phpthemis_scell_seal_decrypt( string $password, string $encrypted_data [, string $context] )

Parameters:

  • password the password to be used for decryption.
  • encrypted_data the data to be decrypted.
  • context the optional context (see above).

Return Values

On success returns a string of binary data containing the original data. NULL is returned on error.

Example
$password = 'not-so-secret';
$message  = 'The best laid schemes of mice and men go oft awry';
$context  = '12345';
$secure_cell = phpthemis_scell_seal_encrypt($password, $message, $context);
$decrypted   = phpthemis_scell_seal_decrypt($password, $secure_cell, $context);
echo "Decrypted: $decrypted\n";
Secure Cell Token protect Mode

For cases where it is not feasible to simply append authentication data to the encrypted data, the phpthemis_scell_token_protect_encrypt and phpthemis_scell_token_protect_decrypt functions allow these two elements to be handled separately.

Encryption
mixed phpthemis_scell_token_protect_encrypt( string $password, string $source_data [, string $context] )

Parameters:

  • password the password to be used for encryption.
  • source_data the data to be encrypted.
  • context the optional context (see above).

Return Values

Returns an associative array, the elements of which are strings of binary data containing the encrypted data and the authentication data on success. NULL is returned on error.

Decryption
mixed phpthemis_scell_token_protect_decrypt( string $password, string $encrypted_data, string $token [, string $context] )

Parameters:

  • password password to be used for decryption.
  • encrypted_data data to be decrypted.
  • token relevant authentication data (token).
  • context optional context (see above).

Return Values

Returns a string of binary data containing the original data on success. NULL is returned on error.

Example
$password = 'not-so-secret';
$message  = 'The best laid schemes of mice and men go oft awry';
$context  = '12345';
$secure_cell = phpthemis_scell_token_protect_encrypt($password, $message, $context);
$decrypted = phpthemis_scell_token_protect_decrypt($password, $secure_cell['encrypted_message'], $secure_cell['token'], $context);
echo "Decrypted: $decrypted\n";
Secure Cell Context Imprint Mode

When it's impossible to simply append authentication data to the encrypted data and when there are no auxiliary storage media to retain the authentication data, the phpthemis_scell_context_imprint_encrypt and phpthemis_scell_context_imprint_decrypt functions provide encryption with the user supplied context, but without the benefit of authentication. This means that the integrity of the data cannot be enforced and these functions should only be the preferred choice when the alternatives above are not viable.

NOTE: In Context Imprint mode, the context is mandatory.

Encryption
mixed phpthemis_scell_context_imprint_encrypt( string $password, string $source_data, string $context )

Parameters:

  • password the password to be used for encryption.
  • source_data the data to be encrypted.
  • context the mandatory context (see above).

Return Values

Returns a string of binary data containing the encrypted data without any authentication data on success. NULL is returned on error.

Decryption
mixed phpthemis_scell_context_imprint_decrypt( string $password, string $encrypted_data, string $context )

Parameters:

  • password the password to be used for decryption.
  • encrypted_data the data to be decrypted.
  • context the mandatory context (see above).

Return Values

Returns a string of binary data containing the original data on success. NULL is returned on error.

Example
$password = 'not-so-secret';
$message  = 'The best laid schemes of mice and men go oft awry';
$context  = '12345';
$secure_cell = phpthemis_scell_context_imprint_encrypt($password, $message, $context);
$decrypted   = phpthemis_scell_context_imprint_decrypt($password, $secure_cell, $context);
echo "Decrypted: $decrypted\n";

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.

As noted above, Secure Session is stateful. It is therefore implemented by phpthemis as an object rather than as static functions. It's important to note at this point that persisting a Secure Session object (for example across HTTP requests to PHP as either a CGI or an Apache Module) would:

a) present a range of unwanted security issues and

b) is simply not supported.

Thus PHP use of Secure Session should only be considered in the context of daemonised PHP processes and there are definitely alternatives to that you may wish to consider.

NOTE: The phpthemis implementation uses exceptions to handle error states.

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.

In order to focus on the specific functionality of Secure session (rather than the communication required between the client and the server), the example code below simulates a client / server interaction.

The Callback for Peer Public Key Access
function get_pub_key_by_id($id) {
    global $key_store;
    $pubkey = '';
    if(!empty($key_store[$id])) {
        $pubkey = $key_store[$id]['public_key'];
    }
    return($pubkey);
}

The function name get_pub_key_by_id is required. The function should return a public key generated by phpthemis_gen_ec_key_pair or phpthemis_gen_rsa_key_pair.

Initialisation

The client and server keys are generated and stored in a global (accessible to the get_pub_key_by_id) function above. Both the client and the server session objects are constructed.

Secure Session is established and messages are exchanged.

NOTE: The second and the third message do not require session negotiation.

global $key_store; // An arbitrary global

//Set up client and server keys
$key_store['server'] = phpthemis_gen_ec_key_pair();
$key_store['client'] = phpthemis_gen_ec_key_pair();

// Create Secure Session Objects
try {
    $server_session = new themis_secure_session('server', $key_store['server']['private_key']); 
    $client_session = new themis_secure_session('client', $key_store['client']['private_key']);
}
catch (Exception $e) {
    echo "Session setup failed ...".$e->getMessage()."\n";
    exit;
}

// Test messages:
$response=secure_session_client('This is a test message 1',$client_session,$server_session);
echo "Receiving ... (".$response['status'].") ".$response['message']."\n";
$response=secure_session_client('This is a test message 2',$client_session,$server_session);
echo "Receiving ... (".$response['status'].") ".$response['message']."\n";
$response=secure_session_client('This is a test message 3',$client_session,$server_session);
echo "Receiving ... (".$response['status'].") ".$response['message']."\n";
The client side

Initially, if the session is not established, the client generates a connect request. Subsequently, the client "negotiates" with the server by replying with its stateful interpretation of the server's response. Once "negotiation" is complete, the initial application level message is sent and the server's response is processed.

function secure_session_client($message_to_send,$client_session,$server_session) {
    $client_message = '';
    $server_response = array('status' => 0, 'message' => '');
    try {
        if (!$client_session->is_established()) {
            echo "Connecting ... \n";
            $client_message = $client_session->connect_request();
        }

        $ii = 1;
        while (!$client_session->is_established()) {
            echo "Negotiating ... ".$ii++."\n";
            $server_response=secure_session_server($client_message,$server_session);
            if ($server_response['status'] != 0) {
                return($server_response);
            }
            $client_message=$client_session->unwrap($server_response['message']);
        }

        // With the session established handle the actual message to send
        echo "Sending ... ".$message_to_send." \n";
        $client_message = $client_session->wrap($message_to_send);
        $server_response = secure_session_server($client_message,$server_session);
        if ($server_response['status'] != 0) {
            return($server_response);
        }
        $server_response['message'] = $client_session->unwrap($server_response['message']);
    }
    catch (Exception $e) {
        $server_response['status'] = -2; // A Client Error
        $server_response['message'] = $e->getMessage();
    }
    return($server_response);
}
The server side

A minimal "application" level protocol returns an associative array containing "status" (0 for success, -1 on error) and "message" containing either the negotiation phase data, the application level response, or the exception message on error.

function secure_session_server($client_message,$server_session) {
    $server_response = array('status' => 0, 'message' => '');
    try {
        if (!$server_session->is_established()) {
            $server_response['message'] = $server_session->unwrap($client_message);
        } else {
            $client_message = $server_session->unwrap($client_message);
            $server_response['message'] = $server_session->wrap('Response to: '.$client_message);
        }
    }
    catch (Exception $e) {
        $server_response['status'] = -1; // A Server Error
        $server_response['message'] = $e->getMessage();
    }   
    return($server_response);
}

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

Using Themis in Ruby

Introduction

The Ruby extension (wrapper) provides access to the features and functions of the Themis cryptographic library:

  • Key generation: creation of public/private key pairs, used in Secure Message and Secure Session.
  • Secure Message: 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: a session between two peers, within which the data can be securely exchanged with higher security guarantees. EC + ECDH, AES container.
  • Secure Comparator: comparison of a secret between two parties without leaking anything related to the secret: Zero-Knowledge Proof-based authentication system. Hardened Socialist Millionaire Protocol + ed25519.

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

There are also example console utils available for the Ruby wrapper for Themis (as well as for some other wrappers — see the full list here). They help understand the specific mechanics of encryption/decryption processes of this specific wrapper. You can find the example console utils for the Ruby wrapper here.

Supported versions

rbthemis is tested on ruby 2.4 and higher.

Quickstart

Installing stable version from RubyGems

RubyThemis wrapper is available on RubyGems. 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.

2. Install RubyThemis gem: gem install rbthemis

NOTE: You'll need to add sudo if you're installing it system-wide, not just in a dedicated or virtual user environment.

⚠️ IMPORTANT: Recently we have renamed the gem to rbthemis. If you were previously using an older version called rubythemis, please uninstall it from your system using bash gem uninstall rubythemis before installing rbthemis.

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.

IMPORTANT: Remember that 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.

1. Build and install Themis Core library into your system.

2. Build and install RubyThemis wrapper from source code.

Importing Themis

Add the following to your code:

require 'rbthemis'

and you're good to go!

Examples for Ruby

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 necessary 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
class SKeyPairGen
  def ec
  def rsa
end

Description:

  • ec
    Return private key and public key strings for Elliptic Curve algorithm.
    Raises ThemisError on failure.

  • rsa
    Return private key and public key strings for RSA algorithm.
    Raises ThemisError on failure.

Example
generator = Themis::SKeyPairGen.new
private_key, public_key = generator.ec
private_key, public_key = generator.rsa

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 Smessage
  def initialize(private_key, peer_public_key)
  def wrap(message)
  def unwrap(message)
end

def s_sign(private_key, message)
def s_verify(peer_public_key, message)

Description:

  • Smessage: encrypted secure message.

  • initialize(private_key, peer_public_key)
    Initialise encrypted Secure Message object with private_key and peer_public_key.
    Raises ThemisError on failure.

  • wrap(message)
    Encrypt message.
    Return encrypted Secure Message container as a binary string.
    Raises ThemisError on failure.

  • unwrap(message)
    Decrypt binary message. Return plaintext. Raises ThemisError on failure.

  • Signed secure message

  • s_sign(private_key, message)
    Sign message with private_key.
    Return signed Secure Message container as a binary string.
    Raises ThemisError on failure.

  • s_verify(peer_public_key, message)
    Verify message with peer_public_key. Peer public key should be from the same keypair as private key.
    Return original message without signature.
    Raises ThemisError on failure.

Example

For a detailed explanation of Secure Message, see Secure Message description.

Initialise encrypter:

smessage = Themis::Smessage.new(private_key, peer_public_key)

Encrypt message:

begin
  encrypted_message = smessage.wrap(message)
rescue ThemisError => e
  # error occured
end

Decrypt message:

begin
  decrypted_message = smessage.unwrap(encrypted_message)
rescue ThemisError => e
  # error occured
end

Sign message:

begin
  signed_message = Themis.s_sign(private_key, message)
rescue ThemisError => e
  # error occured
end

Verify message:

Peer public key should be from the same keypair as private key.

begin
  message = Themis.s_verify(peer_public_key, message_signed_by_peer)
rescue ThemisError => e
  # error occured
end

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 interface:
class Scell
  def initialize(key, mode)
  def encrypt(message, context = nil)
  def decrypt(message, context = nil)
end

Description:

  • initialize(key, mode)
    Initialise Secure Cell object with master key and mode. Where mode is one of:
  • SEAL_MODE - for Seal mode.
  • TOKEN_PROTECT_MODE - for Token-Protect mode.
  • CONTEXT_IMPRINT_MODE - for Context-Imprint mode.

  • encrypt(message, context = nil)
    Encrypt message with optional context.
    Return encrypted string in seal and context-imprint modes.
    Return an array of two strings (encrypted string and token) in token-protect mode.
    Raises ThemisError on failure.

  • decrypt(message, context = nil)
    Decrypt message with optional context.
    (For token-protect mode pass an array of message and token.)
    Return decrypted string.
    Raises ThemisError on failure.

Examples for different modes

Secure Cell Seal Mode
scell_seal = Themis::Scell.new(key, Themis::Scell::SEAL_MODE)

encrypted_message = scell_seal.encrypt(message, context)

decrypted_message = scell_seal.decrypt(encrypted_message, context)
Secure Cell Token-Protect Mode
scell_token = Themis::Scell.new(key, Themis::Scell::TOKEN_PROTECT_MODE)

encrypted_message, token = scell_token.encrypt(message, context)

decrypted_message = scell_token.decrypt([encrypted_message, token], context)
Secure Cell Context-Imprint Mode

NOTE: Context is required in context-imprint mode.

scell_context = Themis::Scell.new(key, Themis::Scell::CONTEXT_IMPRINT_MODE)

encrypted_message = scell_context.encrypt(message, context)

decrypted_message = scell_context.decrypt(encrypted_message, context)

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 Ssession
  def initialize(id, private_key, transport)
  def established?
  def connect_request
  def wrap(message)
  def unwrap(message)
end

Description:

  • initialize(id, private_key, transport)
    Initialize Secure Session object with peer id, private_key and a transport — reference to callback object with the following interface: ruby class transport < Themis::Callbacks def get_pub_key_by_id(id) # - return public_key associated with id end Raises ThemisError on failure.

  • established?
    Checks whether the connection has been established.
    Raises ThemisError on failure.

  • connect_request
    Create connection initialisation message, send it to the peer.
    Raises ThemisError on failure.

  • wrap(message)
    Encrypts user message for sending.
    Raises ThemisError on failure.

  • unwrap(message)
    Decrypts message from the peer.
    Return a pair of status code and message. If the status is Themis::SEND_AS_IS, the returned message is a connection reply that must be sent to the peer without any corrections. Otherwise it is a decrypted user message received from the peer.
    Raises ThemisError on failure.

Secure Session Workflow

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

NOTE: We consider wrap/unwrap more fit for typical Ruby frameworks (we've looked into Ruby on Rails and Eventmachine), so currently Secure Session supports wrap/unwrap mode only. However, if you find that a fully-automatic send/receive mode might be a good use case for something, let us know.

Secure Session has two parties 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).

Wrap/Unwrap

Initialise callbacks:

class CallbacksForThemis < Themis::Callbacks
  def get_pub_key_by_id(id)
    # retrieve public_key for id from trusted storage (file, db, etc.)
    return public_key
  end
end
Secure Session client

First, initialisation:

@callbacks = CallbacksForThemis.new
@session = Themis::Ssession.new(id, client_private_key, @callbacks)

# only the client calls this method
connect_request = @session.connect_request
send_to_server(connect_request)

connection_message = receive_from_server()
res, message = @session.unwrap(connection_message)

while res == Themis::SEND_AS_IS
  send_to_server(message)

  connection_message = receive_from_server()
  res, message = @session.unwrap(connection_message)
end

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

To encrypt an outgoing message use:

encrypted_message = @session.wrap(message)
send_to_server(encrypted_message)

To decrypt the received message use:

encrypted_message = receive_from_server()
message = @session.unwrap(encrypted_message)
Secure Session server

First, initialise everything:

@callbacks = CallbacksForThemis.new
@session = Themis::Ssession.new(id, server_private_key, @callbacks)

# there is not connect_request call - it is a server

# receive message from client
connection_message = receive_from_client()
res, message = @session.unwrap(connection_message)

while res == Themis::SEND_AS_IS
  send_to_client(message)

  connection_message = receive_from_client()
  res, message = @session.unwrap(connection_message)
end

Secure Session is ready. Sending and receiving encrypted messages are the same.

Send encrypted:

encrypted_message = @session.wrap(message)
send_to_client(encrypted_message)

Receive and decrypt:

encrypted_message = receive_from_client()
message = @session.unwrap(encrypted_message)

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

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 Scomparator
   def initialize(shared_secret)
   def begin_compare
   def proceed_compare(control_message)
   def result
end

Description:

  • initialize(shared_secret)
    Initialise Secure Comparator object with a shared_secret to compare.
    Raises ThemisError on failure.

  • begin_compare
    Return a Secure Comparator initialisation message.
    Raises ThemisError on failure.

  • proceed_compare(control_message)
    Process control_message and return the next one.
    Raises ThemisError on failure.

  • result
    Return the status of comparison:

  • NOT_READY – continue calling proceed_compare
  • MATCH or NOT_MATCH – comparison result
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
@comparator = Themis::Scomparator.new('Test shared secret')

# this call is specific to the client
comparison_message = @comparator.begin_compare

while @comparator.result == Themis::Scomparator::NOT_READY do
    user_send_function(comparison_message)
    comparison_message = user_receive_function()
    comparison_message = @comparator.proceed_compare(comparison_message)
end

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

Secure Comparator server
@comparator = Themis::Scomparator.new('Test shared secret')

while @comparator.result == Themis::Scomparator::NOT_READY do
    comparison_message = user_receive_function()
    comparison_message = @comparator.proceed_compare(comparison_message)
    user_send_function(comparison_message)
end

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

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

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 for C++

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.

Using Themis with Go

Introduction

The gothemis package provides access to the features and functions of the Themis cryptographic library:

  • Key generation: creation of public/private key pairs, used in Secure Message and Secure Session.
  • Secure Message: 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: a session between two peers, within which the data can be securely exchanged with higher security guarantees. EC + ECDH, AES container.
  • Secure Comparator: comparison of a secret between two parties without leaking anything related to the secret: Zero-Knowledge Proof-based authentication system. Hardened Socialist Millionaire Protocol + ed25519.

There are also example console utils available for the Go wrapper for Themis (as well as for some other wrappers — see the full list here). They help understand the specific mechanics of encryption/decryption processes of this specific wrapper. You can find the example console utils for the Go wrapper here.

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

Supported versions

GoThemis is tested and supported on Go 1.9+

Quickstart

Installing stable version from packages

The easiest way to install Themis is to use package managers.

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

⚠️ IMPORTANT: GoThemis 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.

2. Use go get to install GoThemis wrapper: bash go get github.com/cossacklabs/themis/gothemis/...

Switching version of GoThemis

If you have a proper Go installation and GOPATH set, visit the GoThemis package in GOPATH and check out the specific branch. For example, to get the latest stable version of GoThemis:

cd $GOPATH/src/github.com/cossacklabs/themis/gothemis
git checkout stable

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.

⚠️ IMPORTANT: Remember that 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.

1. Build and install Themis Core library into your system.

2. Use go get -u to install the latest version of GoThemis wrapper: bash go get -u github.com/cossacklabs/themis/gothemis/...

Importing Themis into your project

Add relevant modules to your code:

import "github.com/cossacklabs/themis/gothemis/cell"
import "github.com/cossacklabs/themis/gothemis/compare"
import "github.com/cossacklabs/themis/gothemis/keys"
import "github.com/cossacklabs/themis/gothemis/message"
import "github.com/cossacklabs/themis/gothemis/session"

And you're good to go!

Examples for Go

  • gothemis test files are simple self-describing and easy-to-understand examples of our APIs usage scenarios. Feel free to explore them.

  • More code samples for Themis objects are available in the docs/examples/go 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 necessary 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.

Generating key pairs

You can generate key pairs like this:

import "github.com/cossacklabs/themis/gothemis/keys"

keyPair, err := keys.New(keys.TypeEC) /* or keys.TypeRSA */
priv := keyPair.Private
pub := keyPair.Public

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.

Processing messages

1. Create SecureMessage object with your PrivateKey and recipient's PublicKey:

import "github.com/cossacklabs/themis/gothemis/message"

// for encryption & decryption
encryptor := message.New(yourPrivateKey, peerPublicKey)

// for signing messages
signer := message.New(yourPrivateKey, nil)

// for signature verification
verifier := message.New(nil, peerPublicKey)

For signing/verifying make sure that you use keys from the same keypair – private key for signing message and public key for verifying message.

2. Process each outgoing message:

encryptedMessage, err := encryptor.Wrap(messageToSend)
signedMessage, err := signer.Sign(messageToSign)

3. Process each incoming message:

receivedMessage, err := encryptor.Unwrap(encryptedMessage)
verifiedMessage, err := verifier.Verify(signedMessage)

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

Initialising Secure Cell

Create SecureCell object to protect your data:

import "github.com/cossacklabs/themis/gothemis/cell"

secureCell := cell.New(secretKey, cell.ModeSeal)
// or in other modes:
//   - cell.ModeTokenProtect
//   - cell.ModeContextImprint

NOTE: Read more about Secure Cell modes and which to choose here.

Secure Cell Seal Mode

Initialise cell:

secureCell := cell.New(secretKey, cell.ModeSeal)

Encrypt:

// context may be "nil"
protectedData, _, err := secureCell.Protect(data, context)

Second result _ is additional data that is always nil in this mode.

Decrypt:

The context should be same as in the protect function call for successful decryption (and may be nil).

data, err := secureCell.Unprotect(protectedData, nil, context)
Secure Cell Token-Protect Mode

Initialise cell:

secureCell := cell.New(secretKey, cell.ModeTokenProtect)

Encrypt:

// context may be "nil"
protectedData, additionalData, err := secureCell.Protect(data, context)

In this mode, the result has additional data (which is opaque to the user but is necessary for successful decryption).

Decrypt:

The context should be the same as in the protect function call for successful decryption.

data, err := secureCell.Unprotect(protectedData, additionalData, context)
Secure Cell Context-Imprint Mode

Initialise cell:

secureCell := cell.New(secretKey, cell.ModeContextImprint)

Encrypt:

// context is *required* in context-imprint mode
protectedData, _, err := secureCell.Protect(data, context)

Second parameter _ is additional data that is nil in this mode.

Decrypt:

The context should be the same as in the protect function call for successful decryption.

data, err := secureCell.Unprotect(protectedData, nil, context)

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 Workflow

Secure Session has two parties 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).

Using Secure Session

1. Implement session.SessionCallbacks interface: * GetPublicKeyForId which will return peer's trusted public key when needed by the system * StateChanged is just a notification callback. You may use it for informational purpose, to update your UI or just have a dummy (do-nothing) implementation

import "github.com/cossacklabs/themis/gothemis/session"
import "github.com/cossacklabs/themis/gothemis/keys"

type callbacks struct {
        // ...
}

func (clb *callbacks) GetPublicKeyForId(ss *session.SecureSession, id []byte) (*keys.PublicKey) {
        pub := getPublicKeyFromDatabaseOrSomeOtherStorageOrSource(id)

        return pub // or nil if key was not found
}

func (clb *callbacks) StateChanged(ss *session.SecureSession, state int) {
        // Do something if you wish.
        // state constants:
        //   - session.StateIdle
        //   - session.StateNegotiating
        //   - session.StateEstablished
}

2. Create SecureSession object:

session, err := session.New(yourId, yourPrivateKey, &callbacks{})

3. On the client side, initiate Secure Session negotiation by generating and sending connection request:

connectRequest, err = session.ConnectRequest();
// send connectRequest to the server

4. Start receiving and parsing incoming data on both sides:

// receive some data and store it in receiveBuffer

// "receiveBuffer" contains encrypted data from your peer,
// try decrypting it...
data, sendPeer, err := session.Unwrap(receiveBuffer)
if err != nil {
        // handle error
}
if sendPeer {
        // "receiveBuffer" is a part of the negotiation protocol:
        // so "data" contains the response to this protocol,
        // which needs to be forwarded to your peer as is.

        // Just send "data" to your peer.
} else {
        // "data" may be nil on the client when the Secure Session
        // completes connection negotiation.
        if data != nil {
                // Now "data" contains decrypted data.

                // Process "data" according to your application.
        }
}

5. When the protocol negotiation finishes, you may send the encrypted data to your peer:

wrappedData, err := session.Wrap(yourData);
// send wrappedData to your peer

Please see the Secure Session test and Secure Session Examples to get the complete vision how Secure Session works.

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 workflow

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

Secure Comparator client
import "github.com/cossacklabs/themis/gothemis/compare"

// create secure comparator and append secret
scomparator, err := compare.New()
err = scomparator.Append(sharedSecret) // some byte[]

// Client initiates secure comparison.
buffer, err := scomparator.Begin()

for {
        // Stop if the comparison is ready.
        res, err := scomparator.Result()
        if res != compare.NotReady {
                break
        }

        // Send "buffer" to the server and receive "reply".

        // Proceed with comparison...
        buffer, err = scomparator.Proceed(reply)
}

res, err := scomparator.Result()
if err != nil {
        // handle failed comparison
}
if res == compare.Match {
        fmt.Println("match")
} else {
        fmt.Println("not match")
}

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

Secure Comparator server

The server part can be described in any language, but let's pretend here that both client and server are using Go.

import "github.com/cossacklabs/themis/gothemis/compare"

// create secure comparator and append secret
scomparator, err := compare.New()
err = scomparator.Append(sharedSecret) // byte[]

// The server does not initiate connection.

for {
        // Stop if the comparison is ready.
        res, err := scomparator.Result()
        if res != compare.NotReady {
                break
        }

        // Receive "buffer" from the client.

        // Proceed with comparison...
        reply, err = scomparator.Proceed(buffer)

        // Send "reply" back to the client, if not empty.
}

res, err := scomparator.Result()
if err != nil {
        // handle failed comparison
}
if res == compare.Match {
        fmt.Println("match")
} else {
        fmt.Println("not match")
}

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

Please see the Secure Comparator Examples to gain a complete understanding of how Secure Comparator works.

Using Themis in Node.js

Introduction

The Node.js extension provides access to the features and functions of the Themis cryptographic library:

  • Key generation: creation of public/private key pairs, used in Secure Message and Secure Session.
  • Secure Message: 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: a session between two peers, within which the data can be securely exchanged with higher security guarantees. EC + ECDH, AES container.
  • Secure Comparator: comparison of a secret between two parties without leaking anything related to the secret: Zero-Knowledge Proof-based authentication system. Hardened Socialist Millionaire Protocol + ed25519.

There are also example console utils available for the NodeJS wrapper for Themis (as well as for some other wrappers — see the full list here). They help understand the specific mechanics of encryption/decryption processes of this specific wrapper. You can find the example console utils for the NodeJS wrapper here.

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

Supported versions

jsthemis v0.11.0 is tested and supported on Node.js v9+. It depends on nan ^2.8.0 package.

If you need compatibility with older Node.js or nan, please refer to the older jsthemis versions.

Quickstart

Installing stable version from npm

JsThemis extension is available on npm. 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: JsThemis 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. Install JsThemis extension via npm: bash npm install jsthemis

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.

⚠️ IMPORTANT: Remember that 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.

1. Build and install Themis Core library into your system.
2. Build and install JsThemis extension from source code.

Importing Themis

Add to your code:

var themis = require('jsthemis')

and you're good to go!

Examples for Node.js

Some code samples for Themis objects are available in docs/examples/js 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 necessary 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
function KeyPair() {
    function private();
    function public();
}
function KeyPair(private, public);

Description:

  • new KeyPair()
    Generates and returns a new Elliptic Curve key pair.
    Raises Error on failure.

  • private()
    Returns private key as Buffer.

  • public()
    Returns public key as Buffer.

  • new KeyPair(private, public)
    Constructs an Elliptic Curve key pair from private and public keys.
    Raises Error on failure.

Example
var key_pair = new themis.KeyPair()
var private_key = key_pair.private().toString("base64")
var public_key = key_pair.public().toString("base64")

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:
function SecureMessage(private_key, peer_public_key) {
   function encrypt(message);
   function decrypt(message);
   function sign(message);
   function verify(message);
}

Description:

  • new SecureMessage(private_key, peer_public_key)
    Create a new Secure Message with private_key and peer_public_key.
    Raises Error on failure.

  • encrypt(message)
    Encrypt message, return encrypted Secure Message container as Buffer.
    Requires both private_key and peer_public_key to be set.
    Raises Error on failure.

  • decrypt(message)
    Decrypt message, return decrypted original message as Buffer.
    Requires both private_key and peer_public_key to be set.
    Raises Error on failure.

  • sign(message)
    Sign message, return signed Secure Message container as Buffer.
    Requires private_key to be set.
    Raises Error on failure.

  • verify(message)
    Verify message signature, return Buffer with original message.
    Requires peer_public_key to be set. Peer public key should be from the same keypair as private key.
    Raises Error on failure.

Example

Initialise encrypter:

var smessage = new themis.SecureMessage(private_key, peer_public_key)
// or
var smessage = new themis.SecureMessage(new Buffer(private_key, "base64"),
                                        new Buffer(peer_public_key, "base64"))

For signing/verifying make sure that you use keys from the same keypair – private key for signing message and public key for verifying message.

Encrypt message:

try {
    var encrypted_message = smessage.encrypt(message)
} catch (error) {
   // handle error
}

Decrypt message:

try {
   var decrypted_message = smessage.decrypt(encrypted_message)
} catch (error) {
   // handle error
}

Sign message:

try {
   var signed_message = smessage.sign(message)
} catch (error) {
   // handle error
}

Verify message:

try {
   var message = smessage.verify(signed_message)
} catch (error) {
   // handle error
}

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 interface:
function SecureCellSeal(key) {
   function encrypt(message, [context]);
   function decrypt(message, [context]);
}

function SecureCellTokenProtect(key) {
   function encrypt(message, [context]);
   function decrypt(message, token, [context]);
}

function SecureCellContextImprint(key) {
   function encrypt(message, context);
   function decrypt(message, context);
}

Description:

  • new SecureCellSeal(key)
    Initialise Secure Cell in seal mode with master key Buffer (must be non-empty).

  • encrypt(message, [context])
    Encrypt message with additional context (optional argument).
    Return encrypted Buffer.
    Raises Error on failure.

  • decrypt(message, [context])
    Decrypt message with additional context (optional argument).
    Return decrypted Buffer.
    Raises Error on failure.

  • SecureCellTokenProtect(key)
    Initialise Secure Cell in token-protect mode with master key Buffer (must be non-empty).

  • encrypt(message, [context])
    Encrypt message with additional context (optional argument).
    Returns an object with two Buffer fields: encrypted message (data) and authentication token (token).
    Raises Error on failure.

  • decrypt(message, token, [context])
    Decrypt message with token and additional context (optional argument).
    Return decrypted Buffer.
    Raises Error on failure.

  • SecureCellContextImprint(key) Initialise Secure Cell in context-imprint mode with master key Buffer (must be non-empty).

  • encrypt(message, context)
    Encrypt message with mandatory context.
    Return encrypted Buffer.
    Raises Error on failure.

  • decrypt(message, context)
    Decrypt message with mandatory context.
    Return decrypted Buffer.
    Raises Error on failure.

Examples for different modes

Secure Cell seal mode

var scell = new themis.SecureCellSeal(key)

var encrypted_message = scell.encrypt(message, context)
var decrypted_message = scell.decrypt(encrypted_message, context)

Secure Cell token-protect mode:

var scell = new SecureCellTokenProtect(key)

var encrypted_array  = scell.encrypt(message, context)
var encrypted_message = encrypted_array.data
var encrypted_token = encrypted_array.token

var decrypted_message = scell.decrypt(encrypted_message, encrypted_token, context)

Secure Cell context-imprint mode:

var scell = new themis.SecureCellContextImprint(key)

// "context" is mandatory for context-imprint mode
var encrypted_message = scell.encrypt(message, context)
var decrypted_message = scell.decrypt(encrypted_message, context)

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
function SecureSession(id, private_key, get_pub_key_by_id) {
  function isEstablished();
  function connectRequest();
  function wrap(message);
  function unwrap(message);
}

Description:

  • new SecureSession(id, private_key, get_pub_key_by_id)
    Initialise Secure Session with peer id, private_key and a get_pub_key_by_id callback.
    The callback is function(peer_id) that gets Buffer with remote peer ID and should returns public key for it (or null).
    Raises Error on failure.

  • isEstablished()
    Checks if the connection has been established.

  • connectRequest()
    Return connection initialisation message as a Buffer. Send it to the server.
    Raises Error on failure.

  • wrap(message)
    Encrypts message and returns encrypted Buffer.
    Raises Error on failure.

  • unwrap(message)
    Decrypts message.
    Returns Buffer that is either decrypted message (if the connection is established), or a connection message that must be sent as is to the peer.
    Raises Error on failure.

Secure Session workflow

Secure Session has two parties 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).

Secure Session client

First, initialisation:

var client_session = new themis.SecureSession(client_id, client_keypair.private(),
  function(id) {
      // Get public key for specified ID from file, database, etc.
      return public_key
  });

var data = client_session.connectRequest()
do {
    // send "data" to server, receive "response"
    data = client_session.unwrap(response)
} while (!client_session.isEstablished());

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

To encrypt the outgoing message use:

var encrypted_message = client_session.wrap(message)
// send "encrypted_message" by any preferred method

To decrypt the received message, use:

// receive "encrypted_message" from peer 
var message = client_session.unwrap(encrypted_message)
Secure Session server

First, initialise everything:

var server_session = new themis.SecureSession(server_id, server_keypair.private(),
  function(id) {
      // Get public key for specified ID from file, database, etc.
      return public_key
  });

while (!server_session.isEstablished()) {
    // receive "request" from client
    reply = server_session.unwrap(request)
    // send "reply" to client
}

Secure Session is ready. See the full example available in docs/examples/js

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
function SecureComparator(sharedSecret) {
  function beginCompare();
  function proceedCompare(message);
  function isCompareComplete();
  function isMatch();
}

Description:

  • new SecureComparator(shared_secret)
    Initialises secure comparison for provided shared_secret.
    Raises Error on failure.

  • beginCompare()
    Returns an initial message for the client, send it to the server.
    Raises Error on failure.

  • proceedCompare(message)
    Processes message and returns a reply to send to the peer.
    Raises Error on failure.

  • isCompareComplete()
    Returns true if the comparison is complete.

  • isMatch()
    Returns boolean result of the comparison.

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
var client = new themis.SecureComparator(new Buffer("shared secret"))

// Client initiates comparison
var data = client.beginCompare()

while (!client.isCompareComplete()) {
    // Send data to server and receive response
    sendDataToServer(data)
    data = receiveFromServer()

    // Process response and prepare reply
    data = client.proceedCompare(data)
}

After the loop finishes, the comparison is over and its result can be checked by calling isMatch():

if (client.isMatch()) {
    // secrets match
} else {
    // secrets don't match
}
Secure Comparator server

The server part can be described in any language, but let's pretend here that both client and server are using JavaScript.

var server = new themis.SecureComparator(new Buffer("shared secret"))

// Receive request, process it, and send back reply (until completion)
while (!server.isCompareComplete()) {
    var data = receiveFromClient()
    data = server.proceedCompare(data)
    sendToClient(data)
}

After the loop finishes, the comparison is over and its result can be checked by calling isMatch:

if (server.isMatch()) {
    // secrets match
} else {
    // secrets don't match
}

This is it. See the full examples in docs/examples/js.

Using Themis in Rust

Introduction

The Rust-Themis extension (wrapper) provides access to features and functions of Themis cryptographic library for applications written in Rust:

  • Key generation: creation of public/private key pairs, used in Secure Message and Secure Session.
  • Secure Message: 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: a session between two peers, within which the data can be securely exchanged with higher security guarantees. EC + ECDH, AES container.
  • Secure Comparator: comparison of a secret between two parties without leaking anything related to the secret: Zero-Knowledge Proof-based authentication system. Hardened Socialist Millionaire Protocol + ed25519.

There are also example console utils available for the Rust wrapper for Themis (as well as for some other wrappers — see the full list). They help understand the specific mechanics of encryption/decryption processes of this specific wrapper.

You can learn more about the Themis library in the general documentation or see a bit of extra info in the additional Rust-Themis API documentation.

Supported versions

Rust-Themis is tested and supported on the stable Rust compiler (Rust 1.31+).

Quickstart

Requirements

Rust-Themis uses pkg-config to locate the core Themis library. Please install pkg-config and make sure it's properly configured (you may need to tweak PKG_CONFIG_PATH if non-standard installation paths are used).

Rust-Themis generates C bindings on the fly with bindgen. Note that it requires libclang to work so you have to install this library. Usually, it's a part of the clang package available for your operating system.

Installing stable version from packages

Rust-Themis wrapper is available on crates.io. 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: Rust-Themis 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.

2. Add the following line to your Cargo.toml file:

[dependencies]
themis = "0.11"

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.

⚠️ IMPORTANT: Remember that 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.

1. Build and install Themis Core library into your system.

2. Add the following line to your Cargo.toml file:

[dependencies]
themis = { path = "/path/to/cloned/themis/repo" }

Configuring pkg-config

Currently, it is necessary to tweak pkg-config configuration on CentOS (regardless of the way that you use to install Themis Core). Please set the following environment variable before using Rust-Themis:

export PKG_CONFIG_PATH=/usr/lib/pkgconfig

Importing Themis

Import the necessary functions and you're good to go:

use themis::keygen::gen_ec_key_pair;

Modules

Rust-themis extension (wrapper) contains the following modules:

  • keygen - generating key material.
  • keys - cryptographic keys.
  • secure_cell - Secure Cell for data storage.
  • secure_comparator - Secure Comparator protocol.
  • secure_message - Secure Message system.
  • secure_session - Secure Session service.

Structs

Error - error type for most Themis operations. Errors are usually caused by invalid, malformed or malicious input as well as incorrect usage of the library. However, they may also result from underlying OS errors.

Enums

ErrorKind - a list of Themis error categories.
This enumeration is used by Error type, returned by most Themis functions. Some error kinds are specific to particular functions, and some are used internally by the library.

Type Definitions

Result - Result type for most Themis operations.

Examples for Rust

There are some examples in the docs/examples/rust directory. You can run them with Cargo:

cargo run --example keygen

These examples illustrate how to use Themis to build simple applications.

You may also find it helpful to peek into tests if you are not sure how to use the API.

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 necessary 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 example
use themis::keygen::gen_ec_key_pair;
use themis::secure_message::SecureMessage;

// Here we generate a new random Elliptic Curve key pair.
let key_pair = gen_ec_key_pair();

let secure = SecureMessage::new(key_pair);

let encrypted = secure.encrypt(b"message")?;
let decrypted = secure.decrypt(&encrypted)?;
assert_eq!(decrypted, b"message");

Description:

gen_ec_key_pair generates a pair of Elliptic Curve (ECDSA) keys.
gen_rsa_key_pair generates a pair of RSA keys.

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 example
use themis::secure_message::SecureMessage;
use themis::keygen::gen_ec_key_pair;

let key_pair = gen_ec_key_pair();

let secure = SecureMessage::new(key_pair);

let encrypted = secure.encrypt(b"message")?;
let decrypted = secure.decrypt(&encrypted)?;
assert_eq!(decrypted, b"message");

Description:

SecureMessage - Secure Message encryption and decryption.
SecureSign - Secure Message signing.
SecureVerify - Secure Message verification.

NOTE: For signing/verifying make sure that you use keys from the same keypair – private key for signing message and public key for verifying message.

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 examples

Here is how you use Secure Cell to seal away your data:

use themis::secure_cell::SecureCell;

let cell = SecureCell::with_key(b"seekryt")?.seal();

let encrypted = cell.encrypt(b"source data")?;
let decrypted = cell.decrypt(&encrypted)?;
assert_eq!(decrypted, b"source data");

Description:

SecureCell - Basic Secure Cell.
SecureCellContextImprint - Secure Cell in context imprint operation mode.
SecureCellSeal - Secure Cell in sealing operation mode.
SecureCellTokenProtect - Secure Cell in token protect operation mode.

Secure Cell Token-protect Mode

In this mode the input data is mixed with the provided context and encrypted, then the authentication token is computed and returned separately, along with the encrypted container. You will have to provide the authentication token later to decrypt the data, but it can be stored or transmitted separately. The encrypted data has the same length as the original input.

use themis::secure_cell::SecureCell;

let cell = SecureCell::with_key(b"password")?.token_protect();

let input = b"test input";
let (output, token) = cell.encrypt(input)?;

assert!(output.len() == input.len());
Secure Cell Context-Imprint Mode

In this mode, the input data is mixed with the provided context and encrypted, but there is no authentication token. Use this mode when you have no additional storage available for the authentication data and you absolutely need the output data to have the same length as the original input.

use themis::secure_cell::SecureCell;

let cell = SecureCell::with_key(b"password")?.context_imprint();

let input = b"test input";
let output = cell.encrypt_with_context(input, b"context")?;

assert!(output.len() == input.len());  

Note that in context imprint mode you must provide non-empty context. Also, keep in mind that Secure Cell cannot verify integrity and correctness of the decrypted data so you have to have some other means in place to validate the output.

Secure Cell Seal mode

In this mode the input data is mixed with the provided context and encrypted, then the authentication tag is appended to the data, resulting in a single encrypted and authenticated container. Note that the resulting sealed cell takes more space than the input data.

use themis::secure_cell::SecureCell;

let cell = SecureCell::with_key(b"password")?.seal();

let input = b"test input";
let output = cell.encrypt(input)?;

assert!(output.len() > input.len());

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

Secure Session usage is relatively involved so you can see s complete working example in the documentation for client and server.

To sum it up, you begin by implementing a SecureSessionTransport. You have to implement at least the get_public_key_for_id method and may want to implement some others. Then you acquire the asymmetric key pairs and distribute the public keys associated with peer IDs — arbitrary byte strings used to identify communicating Secure Sessions. With that, you can create an instance of SecureSession on both the client and the server.

Next, you go through the negotiation stage using connect and negotiate methods until the connection is_established. After that, the Secure Sessions are ready for data exchange which is performed using send and receive methods.

There is also an alternative buffer-oriented API.

Secure Session callback API

NOTE: SecureSessionTransport is an interface you need to provide for Secure Session operation. The only required method is get_public_key_for_id. It is required for public key authentication. Other methods are optional, you can use Secure Session without them, but some functionality may be unavailable.

Secure Session only provides security services and doesn’t do actual network communication. In fact, Secure Session is decoupled and independent from any networking implementation. It is your responsibility to provide network transport for Secure Session using the SecureSessionTransport trait. There are two types of APIs available: callback API and buffer-aware API. You can choose whatever API is more suitable for your application, or you can even mix them when appropriate.

Callback API

With the callback API, you delegate network communication to Secure Session. In order to use it, you have to implement the send_data and receive_data callbacks of SecureSessionTransport. Then you use connect and negotiate methods to negotiate and establish a connection. After that, send and receive methods can be used for data exchange. Secure Session will synchronously call the provided transport methods when necessary to perform network communication.

There is an example of server using the callback API available.

Buffer-aware API

With the buffer-aware API, you are responsible for transporting Secure Session messages between peers. Secure Session does not use send_data and receive_data callbacks in this mode. Instead, the connect_request and negotiate_reply methods return and receive data buffers that have to be exchanged between peers via some external transport (e.g., TLS). Similarly, wrap and unwrap methods are used to encrypt and decrypt data exchange messages after the connection has been negotiated. They too accept plaintext messages and return encrypted containers or vice versa.

There is an example of a using the buffer-aware API available.

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 workflow

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

Before initiating the protocol both parties should append their secrets to be compared. This can be done incrementally so even multi-gigabyte data sets can be compared with ease.

use themis::secure_comparator::SecureComparator;

let mut comparison = SecureComparator::new();

comparison.append_secret(b"999-04-1234")?;

After that, the client initiates the comparison and runs a loop:

let mut request = comparison.begin_compare()?;

while !comparison.is_complete() {
    send(&request);         // This function should send the `request` to the server.
    let reply = receive();  // This function should receive a `reply` from the server.

    request = comparison.proceed_compare(&reply)?;
}

if !comparison.result()? {
    unimplemented!("handle failed comparison here");
}

While the server does almost the same thing:

while !comparison.is_complete() {
    // This function should receive a `request` from the client.
    let request = receive();

    let reply = comparison.proceed_compare(&request)?;

    send(&reply);   // This function should send the `reply` to the client.
}

if !comparison.result()? {
    unimplemented!("handle failed comparison here");
}

Both the server and the client use result to get the comparison result after it is_complete.

Developing Themis for Rust

Rust-Themis uses standard Cargo tooling so developing the library itself is equally easy. Check out the latest source code from GitHub:

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

Then you can go ahead and, for example, build and run the test suite:

cargo test

or build and read the latest API documentation:

cargo doc --open

You can test your application with your local working copy of Themis by adding the following override to the application's Cargo.toml file:

[patch.crates-io]
themis = { path = "/path/to/themis/repo" }

We use the following Cargo tools to maintain the quality of the code base:

  • rustfmt: automated code formatting keeps the code style consistent.

  • clippy: static code analyzer detects possible issues early on.

If you wish to help develop and expand Rust-Themis, we strongly advise you to install and use these tools.

Please follow the general contribution process outlined in our guide.

Onepage tutorials for Themis

You've reached the end of the onepage tutorials for Acra. You can also find all the tutorials at their separate pages through the Tutorials and How Tos section of the Acra product page.