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:

There are other materials that might help you learn Themis better:

Wrote something about Themis? Let us know and we'll feature your blog post here and in Cossack Labs Twitter.

Using Themis with Java / Android

Introduction

The Java / Android Themis library provides access to the features and functions of the Themis cryptographic library:

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

Examples

Quickstart

Building Themis for Java

Requirements

In addition to the common set of build tools, Themis currently requires either the OpenSSL or LibreSSL package with the developer version of the package (as it provides header files). JDK is also required for building a Java project. More information on getting and installing JDK is here.

Installing Themis and Themis JNI wrapper

Get Themis source code from GitHub:

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

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

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

make install

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

Install the Themis JNI wrapper:

make themis_jni

To build JNI code for Java, the build system needs to know the location of jni.h header. By default, it will be looking for the file in $(JAVA_HOME)/include directory. You may specify a different location by setting JDK_INCLUDE_PATH environment variable before running the make commands provided above.

On Mac OS, you might need to explicitly point to Java headers:

export CFLAGS=-I/System/Library/Frameworks/JavaVM.framework/Headers
make themis_jni

If you want to compile themis_jni with BoringSSL, make sure to install BoringSSL as described in Building and installing section, then run:

make clean ENGINE=boringssl CRYPTO_ENGINE_PATH=boringssl ENGINE_INCLUDE_PATH=$BORINGSSL_HOME/boringssl/include ENGINE_LIB_PATH=$BORINGSSL_HOME/boringssl/build/crypto BUILD_PATH=build_with_boringssl_jni all install themis_jni

In the output folder (/build, you will see libthemis_jni shared library).

To build and run core tests, you can use:

make test

All the tests need to be passed with no errors.

Using Themis in your Java project

Use reference .java files from src/wrappers/themis/java/com/cossacklabs/themis in your project build system.

Building Themis for Android

A quick start using Docker

If you have Docker and you just need to build Themis quickly, you can use our prebuilt android-build image:

docker run --rm -it -v $(pwd):/projects cossacklabs/android-build bash -c 'git clone https://github.com/cossacklabs/themis.git && cd themis && git submodule update --init && ./gradlew assembleRelease'
Requirements

Themis for Android uses Gradle build system. A Gradle wrapper is included into the root directory of the project. Like the rest of the modern Android ecosystem, Themis uses BoringSSL as a cryptographic backend. BoringSSL is included as a submodule in the Themis repository.

You should install the latest Android SDK tools/Android Studio, but you don't need full Android Studio to build Themis, just the command line SDK tools.

Compile Themis AAR

Specify paths to your SDK for Gradle:

export ANDROID_HOME=/path/to/android/sdk

Make sure your SDK has the necessary build tools and Android NDK:

$ANDROID_HOME/tools/bin/sdkmanager --update
$ANDROID_HOME/tools/bin/sdkmanager 'build-tools;27.0.3' 'platforms;android-27' 'ndk-bundle'

Since BoringSSL is included as a submodule, make sure all the submodules are initialised in your cloned Themis repository:

git submodule update --init

Build Themis for Android (both Java and Native part):

./gradlew --info assembleRelease

It will create AAR (Android library project) ready to be used in your application in the build/outputs/aar/ folder.

If you want to use SecureSocket and SecureServerSocket classes, make sure you have declared INTERNET permission in your AndroidManifest.xml.

To run Android tests, connect your device(s) and run (make sure ADB is enabled in settings):

./gradlew --info connectedAndroidTest

Using Themis in your Android project

Import Themis AAR into your application project in Android Studio or reference it in your gradle.build file. More about using libraries here.

Examples

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

  • 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 this 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 the sender supplying a valid public key of the receiver (encrypt/decrypt) or setting this parameter to NULL, or an empty string to use sign/verify.

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 peer's PublicKey:

SecureMessage verifier = new SecureMessage(peerPublicKey);

2. Verify the received messages from your peer:

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

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 when given:

  • input: some source data to protect,
  • key: a password,
  • context: plus an optional "context information",

Secure Cell functions then 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 it is 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 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 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 the cryptographic material is exchanged to generate ephemeral keys;
  • 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 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 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 files 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, which compares and defines if 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 already integrated into the functions and requires minimal integration efforts from the developer.

The workflow of Secure Comparator

Secure Comparator has two participating sides. They are called client and server. The only difference between them is in the order of actions (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 through 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 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 through calling getResult:

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

Using Themis with Swift (iOS/OSX)

Introduction

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

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

Now using Themis in Swift is easy!

Quickstart

Building Themis

Requirements

In addition to the common set of build tools, Themis currently uses OpenSSL under the hood. It is already included in CocoaPods dependencies so you don't have to worry about that!

Installing Themis

Themis for iOS/OSX uses CocoaPods dependency manager.

Add the following lines to your .podfile:

source 'https://github.com/CocoaPods/Specs.git'
platform :ios
pod 'themis'

Using Secure Comparator

If you want to use Secure Comparator, you should enable it passing compiler flag. It's easy to do by adding the following lines to your Podfile:

post_install do |installer_representation|
    installer_representation.pods_project.targets.each do |target|
        target.build_configurations.each do |config|
            config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] ||= ['$(inherited)']
            config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] << 'SECURE_COMPARATOR_ENABLED'
        end
    end
end

And add this to the Bridging Header file:

#define SECURE_COMPARATOR_ENABLED
#import <objcthemis/scomparator.h>
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 as well (available since 0.10.1):

pod 'themis/themis-boringssl'

Examples

Using Themis

Bridging Header

Please add #import <objcthemis/objcthemis.h> into your project's Bridging Headers (something that looks like ProjectName-Bridging-Header.h). Now you can use Themis from any Swift file! (How to create a Bridging Header file?)

Keypair generation

Private/public 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

To generate a pair of keys, objcthemis has a class TSKeyGen. Method init has one parameter - the algorithm type (Elliptic Curve (EC) or RSA algorithms).

/* .EC or .RSA can be used*/
guard let keyGeneratorRSA: TSKeyGen = TSKeyGen(algorithm: .RSA) else {
    print("Error occured while initialising object keyGeneratorRSA", #function)
    return
}
let privateKeyRSA: Data = keyGeneratorRSA.privateKey as Data
let publicKeyRSA: Data = keyGeneratorRSA.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).

Read more about 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: String = "VUVDMgAAAC2ELbj5Aue5xjiJWW3P2KNrBX+HkaeJAb+Z4MrK0cWZlAfpBUql"
let clientPrivateKeyString: String = "UkVDMgAAAC13PCVZAKOczZXUpvkhsC+xvwWnv3CLmlG0Wzy8ZBMnT+2yx/dg"

guard let serverPublicKey: Data = Data(base64Encoded: serverPublicKeyString,
                                           options: .ignoreUnknownCharacters),
    let clientPrivateKey: Data = Data(base64Encoded: clientPrivateKeyString,
                                          options: .ignoreUnknownCharacters) else {
    print("Error occurred during base64 encoding", #function)
    return
}

Initialise encrypter:

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

Encrypt message:

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

var encryptedMessage: Data = Data()
do {
    encryptedMessage = try encrypter.wrap(message.data(using: .utf8))
    print("encryptedMessage = \(encryptedMessage)")

} catch let error as NSError {
    print("Error occurred while encrypting \(error)", #function)
    return
}

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

$ <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
let serverPrivateKeyString: String = "UkVDMgAAAC1FsVa6AMGljYqtNWQ+7r4RjXTabLZxZ/14EXmi6ec2e1vrCmyR"
let clientPublicKeyString: String = "VUVDMgAAAC1SsL32Axjosnf2XXUwm/4WxPlZauQ+v+0eOOjpwMN/EO+Huh5d"

guard let serverPrivateKey: Data = Data(base64Encoded: serverPrivateKeyString,
                                            options: .ignoreUnknownCharacters),
    let clientPublicKey: Data = Data(base64Encoded: clientPublicKeyString,
                                         options: .ignoreUnknownCharacters) else {
    print("Error occurred during base64 encoding", #function)
    return
}

Initialise decrypter:

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

Decrypt message:

do {
    let decryptedMessage: Data = try decrypter.unwrapData(encryptedMessage)
    let resultString: String = String(data: decryptedMessage, encoding: .utf8)!
    print("decryptedMessage->\n\(resultString)")

} catch let error as NSError {
    print("Error occurred while decrypting \(error)", #function)
    return
}

Result:

$ - Knock, knock.\n- Who’s there?\n*very long pause...*\n- Java.!
Sign / verify

The only code difference from encrypt/decrypt modes is the initialiser methods.

let encrypter: TSMessage = TSMessage.init(inSignVerifyModeWithPrivateKey: clientPrivateKey,
                                          peerPublicKey: serverPublicKey)!

let decrypter: TSMessage = TSMessage.init(inSignVerifyModeWithPrivateKey: serverPrivateKey,
                                          peerPublicKey: clientPublicKey)!

Secure Cell

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

The general approach is that given:

  • input: some source data to protect;
  • key: a password;
  • context: plus an optional "context information";

Secure Cell functions will produce:

  • cell: the encrypted data;
  • authentication tag: some authentication data.

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

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

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

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

Secure Cell is available in 3 modes:

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

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

Initialising Secure Cell

To initialise Secure Cell object, use master key in Data format:

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

Initialise encrypter/decrypter:

guard let cellSeal: TSCellSeal = TSCellSeal(key: masterKeyData) else {
    print("Error occurred while initializing object cellSeal", #function)
    return
}

Encrypt:

let message: String = "All your base are belong to us!"
let context: String = "For great justice"

var encryptedMessage: Data = Data()
do {
    // context is optional parameter and may be ignored
    encryptedMessage = try cellSeal.wrap(message.data(using: .utf8)!,
                                         context: context.data(using: .utf8)!)
    print("decryptedMessagez = \(encryptedMessage)")

} catch let error as NSError {
    print("Error occurred while encrypting \(error)", #function)
    return
}

Decrypt:

let context: String = "For great justice"
do {
    let decryptedMessage: Data = try cellSeal.unwrapData(encryptedMessage,
                                               context: context.data(using: .utf8)!)
    let resultString: String = String(data: decryptedMessage, encoding: .utf8)!
    print("decryptedMessage = \(resultString)")

} catch let error as NSError {
    print("Error occurred while decrypting \(error)", #function)
    return
}
Secure Cell Token-protect Mode

Initialise encrypter/decrypter

guard let cellToken: TSCellToken = TSCellToken(key: masterKeyData) else {
    print("Error occurred while initializing object cellToken", #function)
    return
}

Encrypt:

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

var encryptedMessage: TSCellTokenEncryptedData = TSCellTokenEncryptedData()
do {
    // context is optional parameter and may be ignored
    encryptedMessage = try cellToken.wrap(message.data(using: .utf8)!,
                                          context: context.data(using: .utf8)!)
    print("encryptedMessage.cipher = \(encryptedMessage.cipherText)")
    print("encryptedMessage.token = \(encryptedMessage.token)")

} catch let error as NSError {
    print("Error occurred while encrypting \(error)", #function)
    return
}

Decrypt:

let context: String = "I'm a dog"
do {
    let decryptedMessage: Data = try cellToken.unwrapData(encryptedMessage,
                                                          context: context.data(using: .utf8)!)
    let resultString: String = String(data: decryptedMessage, encoding: .utf8)!
    print("decryptedMessage = \(resultString)")

} catch let error as NSError {
    print("Error occurred while decrypting \(error)", #function)
    return
}
Secure Cell Context-Imprint Mode

Initialise encrypter/decrypter

guard let contextImprint: TSCellContextImprint = TSCellContextImprint(key: masterKeyData) else {
    print("Error occurred while initializing object contextImprint", #function)
    return
}

Encrypt

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

var encryptedMessage: Data = Data()
do {
    // context is a REQUIRED parameter here
    encryptedMessage = try contextImprint.wrap(message.data(using: .utf8)!,
                                               context: context.data(using: .utf8)!)
    print("encryptedMessage = \(encryptedMessage)")

} catch let error as NSError {
    print("Error occurred while encrypting \(error)", #function)
    return
}

Decrypt

let context: String = "Microwave"
do {
    // context is a REQUIRED parameter here
    let decryptedMessage: Data = try contextImprint.unwrapData(encryptedMessage,
                                                               context: context.data(using: .utf8)!)
    let resultString: String = String(data: decryptedMessage, encoding: .utf8)!
    print("decryptedMessage = \(resultString)")

} catch let error as NSError {
    print("Error occurred while decrypting \(error)", #function)
    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 bound to a specific session context.

Secure Session operates in two stages:

  • session negotiation where the keys are established and the cryptographic material is exchanged to generate ephemeral keys;
  • data exchange where exchanging of messages can be carried out between peers.

You can read a more detailed description of the process.

Put simply, Secure Session takes place as follows:

  • 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.
  • The client will generate a "connection request" and will dispatch that to the server by whatever means available.
  • The server will enter a negotiation phase in response to the client's "connection 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).

Generate keys

Generate client public and private key. Public key should be sent to the server, and private key is used to initialise Session.

guard let keyGeneratorEC: TSKeyGen = TSKeyGen(algorithm: .EC) else {
    print("Error occurred while initializing object keyGeneratorEC", #function)
    return
}
let privateKeyEC: Data = keyGeneratorEC.privateKey as Data
let publicKeyEC: Data = keyGeneratorEC.publicKey as Data

let privateKeyECString = privateKeyEC.base64EncodedString(options: .lineLength64Characters)
let publicKeyECString = publicKeyEC.base64EncodedString(options: .lineLength64Characters)
Initialise Secure Session

ClientId 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: Data = clientId.data(using: String.Encoding.utf8),
    let clientPrivateKey: Data = Data(base64Encoded: kClientPrivateKey,
                                          options: .ignoreUnknownCharacters) else {
    print("Error occurred during base64 encoding", #function)
    return
}

self.transport = Transport()
self.transport?.setupKeys(kServerId, serverPublicKey: kServerPublicKey)
self.session = TSSession(userId: clientIdData, privateKey: clientPrivateKey, callbacks: self.transport!)
Transport callback

Implement transport callback to return server's public key. Transport layer only returns server public key for requests pointed on serverId.

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
var connectionMessage: Data
do {
    guard let resultOfConnectionRequest = try session?.connectRequest() else {
        throw NSError(domain: "com.themisserver.example", code: -2, userInfo: nil)
    }

    connectionMessage = resultOfConnectionRequest
} catch let error {
    print("Error occurred while connecting to session \(error)", #function)
    return
}

// send `connectionMessage` to the server

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

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

do {
    guard let decryptedMessage = try self.session?.unwrapData(data) else {
        throw NSError(domain: "com.themisserver.example", code: -4, userInfo: nil)
    }

    if let session = self.session, session.isSessionEstablished() == true {
        print("Session established!")
        // session is established: send payload
    } else {
        // session is NOT established yet, send `decryptedMessage` to the server again
    }
} catch let error {
    ...
}

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

Send and receive data
var encryptedMessage: Data
do {
    guard let wrappedMessage: Data = try self.session?.wrap(message.data(using: String.Encoding.utf8)) else {
        print("Error occurred during wrapping message ", #function)
        return
    }
    encryptedMessage = wrappedMessage

    // send `encryptedMessage` to the server
} catch let error {
    print("Error occurred while wrapping message \(error)", #function)
}

...

do {
    guard let decryptedMessage: Data = try self.session?.unwrapData(data),
            let resultString: String = String(data: decryptedMessage, encoding: String.Encoding.utf8) else {

        // handle error
    }
    // resultString contains server response 

} catch let error {
    print("Error occurred while decrypting message \(error)", #function)
}

This is it. See the full example available 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.

First of all, make sure, that you have enabled Secure Comparator, check steps described above.

Secure Comparator client
let sharedMessage = "shared secret"
let client: TSComparator = TSComparator.init(messageToCompare: sharedMessage.data(using: .utf8)!)!

var data = try? client.beginCompare()

while (client.status() == TSComparatorStateType.comparatorNotReady ) {
    // send data on server and receive response
    self.sendDataOnServer(data)
    data = self.receiveServerResponse()

    // proceed and send again
    data = try? client.proceedCompare(data)
}

After the loop finishes, the comparison is over and its result can be checked 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

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

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

var data: Data

while (server.status() == TSComparatorStateType.comparatorNotReady) {
    // receive from client
    data = self.receiveFromClient

    // proceed and send again
    data = try? server.proceedCompare(data)
}

After the loop finishes, the comparison is over and its result can be checked 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/OSX)

Introduction

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

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

Quickstart

Building Themis

Requirements

In addition to the common set of build tools, Themis currently uses OpenSSL under the hood. It is already included in CocoaPods dependencies so you don't have to worry about that!

Installing Themis

Themis for iOS/OSX uses CocoaPods dependency manager.

Add the following lines to your .podfile:

source 'https://github.com/CocoaPods/Specs.git'
platform :ios
pod 'themis'

Using Secure Comparator

If you want to use Secure Comparator, you should enable it passing compiler flag. It's easy to do by adding the following lines to your Podfile:

post_install do |installer_representation|
    installer_representation.pods_project.targets.each do |target|
        target.build_configurations.each do |config|
            config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] ||= ['$(inherited)']
            config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] << 'SECURE_COMPARATOR_ENABLED'
        end
    end
end

... and add to project:

#define SECURE_COMPARATOR_ENABLED
#import <objcthemis/scomparator.h>

Examples

Using Themis

Keypair generation

Private/public 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 protection 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, objcthemis has a class TSKeyGen. Method init has one parameter - the algorithm type (Elliptic Curve (EC) or RSA algorithms).

#import <objcthemis/objcthemis.h>

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

if (!keygenRSA) {
    NSLog(@"%s Error occured while initialising object keygenRSA", sel_getName(_cmd));
    return;
}
NSData * privateKey = keygenRSA.privateKey;
NSData * publicKey = keygenRSA.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).

Read more about 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:

#import <objcthemis/objcthemis.h>

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(@"%s Error occurred while encrypting %@", sel_getName(_cmd), themisError);
    return;
}
NSLog(@"%@", encryptedMessage);

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

$ <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:

#import <objcthemis/objcthemis.h>

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

Decrypt message:

NSData * decryptedMessage = [decrypter unwrapData:encryptedMessage error:&themisError];
if (themisError) {
    NSLog(@"%s Error occurred while decrypting %@", sel_getName(_cmd), themisError);
    return;
}

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

Result:

$ All your base are belong to us!
Sign / verify

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

#import <objcthemis/objcthemis.h>

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

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

Secure Cell

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

The general approach is that given:

  • input: some source data to protect;
  • key: a password;
  • context: plus an optional "context information";

Secure Cell functions will produce:

  • cell: the encrypted data;
  • authentication tag: some authentication data.

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

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

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

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

Secure Cell is available in 3 modes:

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

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

Initialising Secure Cell

To initialise a Secure Cell object, use the master key in NSData format:

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

Initialise encrypter/decrypter:

#import <objcthemis/objcthemis.h>

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

Encrypt:

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

// context is an optional parameter and may be ignored
NSData * encryptedMessage = [cellSeal wrapData:[message dataUsingEncoding:NSUTF8StringEncoding]
                                       context:[context dataUsingEncoding:NSUTF8StringEncoding]
                                         error:&themisError];

if (themisError) {
    NSLog(@"%s Error occurred while enrypting %@", sel_getName(_cmd), themisError);
    return;
}
NSLog(@"encryptedMessage = %@", encryptedMessage);

Decrypt:

NSString * context = @"For great justice";
NSError * themisError;

NSData * decryptedMessage = [cellSeal unwrapData:encryptedMessage
                                         context:[context dataUsingEncoding:NSUTF8StringEncoding]
                                           error:&themisError];
if (themisError) {
    NSLog(@"%s Error occurred while decrypting %@", sel_getName(_cmd), themisError);
    return;
}
NSString * resultString = [[NSString alloc] initWithData:decryptedMessage
                                                encoding:NSUTF8StringEncoding];
NSLog(@"%s resultString = %@", sel_getName(_cmd), resultString);
Secure Cell Token-protect Mode

Initialise encrypter/decrypter

#import <objcthemis/objcthemis.h>

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

Encrypt:

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

// context is an optional parameter and may be ignored
TSCellTokenEncryptedData * encryptedMessage = [cellToken wrapData:[message dataUsingEncoding:NSUTF8StringEncoding]
                                                          context:[context dataUsingEncoding:NSUTF8StringEncoding]
                                                            error:&themisError];
if (themisError) {
    NSLog(@"%s Error occurred while enrypting %@", sel_getName(_cmd), themisError);
    return;
}
NSLog(@"%s\ncipher = %@:\ntoken = %@", sel_getName(_cmd), encryptedMessage.cipherText,encryptedMessage.token);

Decrypt:

NSString * context = @"I'm a dog";
NSError * themisError;

NSData * decryptedMessage = [cellToken unwrapData:encryptedMessage
                                          context:[context dataUsingEncoding:NSUTF8StringEncoding] 
                                            error:&themisError];
if (themisError) {
    NSLog(@"%s Error occurred while decrypting %@", sel_getName(_cmd), themisError);
    return;
}
NSString * resultString = [[NSString alloc] initWithData:decryptedMessage
                                                encoding:NSUTF8StringEncoding];
NSLog(@"%s resultString = %@", sel_getName(_cmd), resultString);
Secure Cell Context-Imprint Mode

Initialise encrypter/decrypter

#import <objcthemis/objcthemis.h>

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

Encrypt

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

// context is NOT an optional parameter here
NSData * encryptedMessage = [contextImprint wrapData:[message dataUsingEncoding:NSUTF8StringEncoding]
                                             context:[context dataUsingEncoding:NSUTF8StringEncoding]
                                               error:&themisError];
if (themisError) {
    NSLog(@"%s Error occurred while enrypting %@", sel_getName(_cmd), themisError);
    return;
}
NSLog(@"%@", encryptedMessage);

Decrypt

NSString * context = @"Microwave";
NSError * themisError;

// context is a REQUIRED parameter here
NSData * decryptedMessage = [contextImprint unwrapData:encryptedMessage
                                               context:[context dataUsingEncoding:NSUTF8StringEncoding]
                                                 error:&themisError];
if (themisError) {
    NSLog(@"%s Error occurred while decrypting %@", sel_getName(_cmd), themisError);
    return;
}
NSString * resultString = [[NSString alloc] initWithData:decryptedMessage
                                                encoding:NSUTF8StringEncoding];
NSLog(@"%s resultString = %@", sel_getName(_cmd), 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 bound to a specific session context.

Secure Session operates in two stages:

  • session negotiation where the keys are established and the cryptographic material is exchanged to generate ephemeral keys;
  • data exchange where exchanging of messages can be carried out between peers.

You can read a more detailed description of the process.

Put simply, Secure Session takes place as follows:

  • 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.
  • The client will generate a "connection request" and will dispatch that to the server by whatever means available.
  • The server will enter a negotiation phase in response to the client's "connection 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).

Generate keys

Generate client public and private key. Public key should be sent to the server, and private key is used to initialise Session.

#import <objcthemis/objcthemis.h>
TSKeyGen * keygenEC = [[TSKeyGen alloc] initWithAlgorithm:TSKeyGenAsymmetricAlgorithmEC];
NSData * privateKey = keygenEC.privateKey;
NSData * publicKey = keygenEC.publicKey;
Initialise Secure Session

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

#import <objcthemis/objcthemis.h>
NSString * userId = [UIDevice currentDevice].name;
self.transport = [Transport new];
self.session = [[TSSession alloc] initWithUserId:[userId dataUsingEncoding:NSUTF8StringEncoding]
                                      privateKey:privateKey
                                       callbacks:self.transport];
Transport callback

Implement transport callback to return the server's public key.

#import <objcthemis/objcthemis.h>
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"]) {
        NSData * key = [[NSData alloc] initWithBase64EncodedString:kServerKey
                                                           options:NSDataBase64DecodingIgnoreUnknownCharacters];
        return key;
    }
    return nil;
}

@end
Connect Secure Session
NSError * error = nil;
NSData * sessionEstablishingData = [self.session connectRequest:&error];

// send `sessionEstablishingData` to the server

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

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

NSError * wrappingError = nil;
NSData * unwrappedData = [self.session unwrapData:responseData error:&wrappingError];
if (wrappingError) {
    // handle error
}

if ([self.session isSessionEstablished]) {
    // session is established: send payload
}

// 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
NSError * error;
NSString * message = @"message to send";
NSData * wrappedData = [self.session wrapData:[message dataUsingEncoding:NSUTF8StringEncoding] error:&error];
NSString * base64URLEncodedMessage = [[wrappedData base64EncodedStringWithOptions:NSDataBase64EncodingEndLineWithLineFeed] 
         stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet alphanumericCharacterSet]];

// send string using NSURLSession or any other kind of transport

...


NSError * error;
NSData * receivedData = [[NSData alloc] initWithBase64EncodedString:message
                                                            options:NSDataBase64DecodingIgnoreUnknownCharacters];
NSData * unwrappedMessage = [self.session unwrapData:receivedData error:&error];

That's it!

Please refer to the docs/examples/Themis-server/Obj-C project 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.

First of all, make sure, that you have enabled Secure Comparator, check steps described above.

Secure Comparator client
NSString * sharedSecret = @"shared secret";
NSData * sharedSecretData = [sharedSecret dataUsingEncoding:NSUTF8StringEncoding];
TSComparator * client = [[TSComparator alloc] initWithMessageToCompare:sharedSecretData];
NSError * error = nil;

NSData * data = [client beginCompare:&error];
while ([client status] == TSComparatorNotReady) {
    // send data on server and receive response
    [self sendDataOnServer:data];
    data = [self receiveResponseFromServer];

    // proceed and send again
    data = [client proceedCompare:data error:&error];
}

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

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

Server part can be described in any language, let's pretend 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 = nil;
NSData * data = nil;

while ([server status] == TSComparatorNotReady) {
    // receive from client
    data = [self receiveFromClient];

    // proceed and send again
    data = [server proceedCompare:data error:&error];
    [self sendDataOnClient:data];
}

After the loop finishes, the comparison is over and its result can be checked 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 provides access to the features and functions of the Themis cryptographic library in Python (2.7+ and 3):

  • 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.
  • Secure Comparator: compare the 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 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.

Quickstart

Requirements

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

Installing Themis

Get Themis source code from GitHub:

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

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

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

make install

If your path is different, please see Building and installing for more details on how to change it (but basically you'll just need to remember into which folder you directed Themis and navigate there).

To build and run tests, you can use:

make test

All the tests need to be passed with no errors.

Building Themis for Python

To install Themis for Python, type:

pip install pythemis

To build and run exactly pythemis test suit (after it's installation), you can use:

make prepare_tests_all test_python

Using Themis in your Python project

Insert:

import pythemis

into your code.

To import a certain module (skeygen for example):

from pythemis import skeygen

Examples

Using Themis

Keypair generation

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

WARNING: When you distribute private keys to your users, make sure 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 or 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 the sender supplying a valid public key of the receiver (encrypt/decrypt) or setting this parameter to NULL, or an empty string to use sign/verify.

Read more about 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. Throws ThemisError on failure.
  • wrap(self, message: bytes) -> bytes - encrypt message. Return encrypted Secure Message container as binary string. Throws ThemisError on failure. Asymmetric keys are needed for Secure Message and Secure Session.
  • 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 with public_key. Return binary string. Throws ThemisError on failure.
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:

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, such as database records or filesystem files. These functions provide both strong symmetric encryption and data authentication mechanisms.

The general approach is that given:

  • input: some source data to protect;
  • key: a password;
  • context: plus an optional "context information";

Secure Cell functions will produce:

  • cell: the encrypted data;
  • authentication tag: some authentication data.

The purpose of the optional "context information" (i.e. a database row number or 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, then decryption will fail.

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

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

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

Secure Cell is available in 3 modes:

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

You can learn more about the underlying considerations, limitations, and features 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 scell seal mode object with key.

  • encrypt(self, message: bytes, context=None: bytes) -> bytes - encrypt message with an optional context. Return encrypted message. Throws ThemisError on failure.

  • def 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: bytes) - initialise scell seal mode object with key.

  • encrypt(self, message: bytes, context=None: bytes) -> bytes - encrypt message with an optional context. Return encrypted message. Throws ThemisError on failure.

  • def 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 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 scell context imprint mode object 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 bound to a specific session context.

Secure Session operates in two stages:

  • session negotiation where the keys are established and the cryptographic material is exchanged to generate ephemeral keys;
  • 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 __del__(self)
   def connect(self)
   def send(self, message: bytes) -> int
   def receive(self) -> bytes
   def connect_request(self) -> bytes
   def wrap(self, message: bytes) -> bytes
   def unwrap(self, message: bytes) -> bytes
   def is_established(self) -> bool

Description:

  • __init__(self, id: bytes, private_key: bytes, transport: TransportStruct) - initialise Secure Session object with id, private_key and transport. Throws ThemisError on failure.

  • __del__(self) - delete Secure Session object correctly.

  • connect_request(self) -> bytes - return a Secure Session initialisation message. Throws ThemisError on failure.

  • connect(self) - create and send to peer a Secure Session initialisation message. Return nothing. Throws ThemisError on failure.

  • wrap(self, message: bytes) -> bytes - return wrapped message. Throws ThemisError on failure.

  • send(self, message: bytes) -> int - create wrapped message and send it to peer. Return internal code status. Throws ThemisError on failure.

  • unwrap(self, message: bytes) -> bytes - return pair: unwrapped message status and unwrapped message. If unwrapped message status equals to THEMIS_CODE.SEND_AS_IS, the unwrapped message must be sent to a peer without any corrections. Throws ThemisError on failure.

  • receive(self) -> bytes - return message received from peer (as plain decrypted message). Throws ThemisError 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).

Communication flow is fully controlled by the Secure Session object

Send/Receive

Initialise 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 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 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 the message from session:

message = ssession.receive()
Secure Session server

First, initialise 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 message works in a manner similar to the client side.

To encrypt an outgoing message, use:

encrypted_message = session.wrap(message)
# send encrypted_message to peer using any method you prefer

To decrypt the received message, use:

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

Communication controlled by user

Secure Session wrap/unwrap

Initialise callback class:

from pythemis.ssession import MemoryTransport


class CustomSimpleTransport(MemoryTransport):
   def __init__(self, *args, **kwargs)
       # initialise 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 define client part
encrypted_message = session.connect_request()

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

# establish 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() method 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)

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 __del__(self)
   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.

  • __del__(self) - delete Secure Comparator object correctly.

  • begin_compare(self) - return a Secure Comparator initialisation message. Throws ThemisError on failure.

  • proceed_compare(self, message) - proceed with current and create the next comparation messages (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 secrets are equal, otherwise return False. 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 defines the client part
comparation_message = comparator.begin_compare()
while not comparator.is_compared():
    user_send_function(comparation_message)
    response = user_recieve_function()
    comparation_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 comparator.is_compared():
     comparation_message = user_receive_function()
     comparation_message = comparator.proceed_compare(comparation_message)
     user_send_function(comparation_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 provides PHP access to the features and functions of the Themis cryptographic library:

  • 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, thereby data can be more securely exchanged. EC + ECDH, AES container.

There are also example console utils available for the PHP 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 PHP wrapper here.

Quickstart

Installing from Packages

If you prefer building from sources — dive here.

Requirements

PHP versions 5.6, 7.0, 7.1 or 7.2 installed in your system.

For Debian or Ubuntu

1. Import the public key used by Cossack Labs to sign packages:

wget -qO - https://pkgs.cossacklabs.com/gpg | sudo apt-key add -

Note: If you wish to validate key fingerprint, it is: 29CF C579 AD90 8838 3E37 A8FA CE53 BCCA C8FF FACB.

2. You may need to install the apt-transport-https package before proceeding:

sudo apt-get install apt-transport-https

3. Add Cossack Labs repository to your sources.list:

sudo add-apt-repository \
    "deb https://pkgs.cossacklabs.com/stable/$(lsb_release -is | awk '{print tolower($0)}') \
    $(lsb_release -cs) \
    main"

4. Update the apt package index:

sudo apt-get update

5. Choose and install Themis extension for your PHP version:

apt-cache search libphpthemis
sudo apt-get install libphpthemis-php<version>

Check for successful setup:

php --ri phpthemis

Building PHPThemis from source code

Requirements

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

Installing Themis

Get Themis source code from GitHub

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

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

In a typical case, all you need to do is (depending on your rights, we assume unprivileged user shell with sudo available):

cd themis
sudo make install

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

To build and run tests, you can use:

make test

All the tests need to be passed with no errors.

Building phpthemis

Once the Themis library is installed, do:

sudo make phpthemis_install

To run tests for php test suit, you can run:

make prepare_tests_all test_php

This will build and install phpthemis.so to the standard zend extension directory. To enable the extension, you should add this to your php.ini file:

extension=phpthemis.so

You can then verify if phpthemis is installed using:

php --ri phpthemis

Examples and Tests

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

Private/public key pair 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

Elliptic Curve

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.

Examples

$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 a public and private key on success. NULL is returned on error. The keys contained in the array values are strings containing binary data.

Examples

$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 the sender supplying a valid public key of the receiver (encrypt/decrypt) or setting this parameter to NULL, or an empty string to use sign/verify.

Read more about 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 — it is assumed 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 — it is assumed that the sender has safely acquired this key through other channels.
  • message 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 encrypted/signed message.

Examples

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:

$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, such as database records or filesystem files. These functions provide both strong symmetric encryption and data authentication mechanisms.

The general approach is that given:

  • input: some source data to protect;
  • key: a password;
  • context: plus an optional "context information";

Secure Cell functions will produce:

  • cell: the encrypted data;
  • authentication tag: some authentication data.

The purpose of the optional "context information" (i.e. a database row number or 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, then decryption will fail.

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

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

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

Secure Cell is available in 3 modes:

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

You can learn more about the underlying considerations, limitations, and features 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 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 bound to a specific session context.

Secure Session operates in two stages:

  • session negotiation where the keys are established and the cryptographic material is exchanged to generate ephemeral keys;
  • 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.

A 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 provides access to the features and functions of the Themis cryptographic library:

  • 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 the key choice), AES GCM containers.
  • Secure Storage (aka Secure Cell): provides secure storage of record-based data through symmetric encryption and data authentication. AES GCM / AES CTR container.
  • Secure Session: establishing a session between two peers, within which the data can be securely exchanged with higher security guarantees. EC + ECDH, AES containers.

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.

Quickstart

Building Themis for Ruby

Requirements

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

Installing Themis

Get the source code of Themis from GitHub:

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

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

In a typical case, all you need to do is:

make && sudo make install

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

To build and run tests, you can use:

make test

All the tests need to be passed with no errors.

Installing Rubythemis

To install Themis for Ruby, type:

gem install rbthemis

Pay attention: recently we have renamed gem to rbthemis. If you were previously using an older version of rubythemis, please uninstall it from your system using gem uninstall rubythemis before installing rbthemis. This change affects only public gem name, function names and imports remains the same.

You can install rubythemis from the following source:

make rubythemis_install

If you want to install rubythemis in the user folder, use GEM_INSTALL_OPTIONS options:

GEM_INSTALL_OPTIONS=--user-install make rubythemis_install

Importing Themis into your project

Add the following to your code:

require 'rubythemis'

To build and run the Ruby test suit (after its installation), you can use:

make prepare_tests_all test_ruby

Examples

Using Themis

Keypair generation

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

WARNING: When you distribute private keys to your users, make sure 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. It is secure enough for exchanging messages from time to time, but if you'd like to have Perfect Forward Secrecy and higher security guarantees, please consider using Secure Session instead.

The Secure Message functions offer two modes of operation:

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

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

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

Secure Message interface:
class 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) - wrap message. Return encrypted Secure Message container as a binary string. Raises ThemisError on failure.

  • unwrap(message) - unwrap message. Return decrypted message. 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 binary string containing signed Secure Message container with public_key. Return plain string. 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:

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, such as database records or filesystem files. These functions provide both strong symmetric encryption and data authentication mechanisms.

The general approach is that given:

  • input: some source data to protect;
  • key: a password;
  • context: plus an optional "context information";

Secure Cell functions will produce:

  • cell: the encrypted data;
  • authentication tag: some authentication data.

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

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

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

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

Secure Cell is available in 3 modes:

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

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

Secure Cell 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 key and mode. mode can be:
  • FULL_MODE - for Seal mode.
  • AUTO_SPLIT_MODE - for Token-protect mode.
  • USER_SPLIT_MODE - for Context-imprint mode.
  • encrypt(message, context = nil) - encrypt message with optional context. Return encrypted string in Seal and Context_imprint modes and 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. Return decrypted string. Raises ThemisError on failure.

Examples

Secure Cell Seal Mode

Initialise encrypter/decrypter

scell_full = Themis::Scell.new(key, Themis::Scell::SEAL_MODE)

Encrypt

encrypted_message = scell_full.encrypt(message, context)

Decrypt

decrypted_message = scell_full.decrypt(encrypted_message, context)
Secure Cell Token-protect Mode

Initialise encrypter/decrypter

scell_auto_split = Themis::Scell.new(key, Themis::Scell::TOKEN_PROTECT_MODE)

Encrypt

encrypted_message, additional_auth_data = scell_auto_split.encrypt(message, context)

Decrypt

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

Initialise encrypter/decrypter

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

Encrypt

encrypted_message = scell_user_split.encrypt(message, context)

Decrypt

decrypted_message = scell_user_split.decrypt(encrypted_message, context)

Secure Session

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

Secure Session operates in two stages:

  • session negotiation where the keys are established and the cryptographic material is exchanged to generate ephemeral keys;

  • data exchange where exchanging of messages can be carried out between peers.

You can read a more detailed description of the process.

Put simply, Secure Session takes place as follows:

  • Both clients and a 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.
  • The client will generate a "connection request" and will dispatch that to the server by whatever means available.
  • The server will enter a negotiation phase in response to the client's "connection 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)
  def finalize
end

Description:

  • initialize(id, private_key, transport) - initialise Secure Session object with id, private_key and a transport - refference to callback object with interface:
class transport < Themis::Callbacks
  def get_pub_key_by_id(id) # return public key string assosiated with id
  def send(message) # send message string to peer
  def receive # return receiver from peer message string
end

Raises ThemisError on failure.

  • connect_request - Create connection initialisation message and send it to peer. Raises ThemisError on failure.

  • wrap(message) - return ready for sending encrypted message. Raises ThemisError on failure.

  • unwrap(message) - return a pair with unwrapped message status and unwrapped message. If unwrapped message status is equal to Themis::SEND_AS_IS unwrapped message must be sent to the peer without any corrections. In other cases, an unwrapped message is a plain 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 the present examples are for wrap/unwrap only. However, if you find that a fully-automatic send/receive might be a good use case for something, let us know.

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 a local storage (see more in Secure Session cryptosystem description).

Wrap/Unwrap

Initialise callbacks class:

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)

# key method call - it is a client
connect_request = @session.connect_request

# send connect_request to server
# receive message from server
res, message = @session.unwrap(message)

while res == Themis::SEND_AS_IS
  # send message to server
end

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

To encrypt an outgoing message, use:

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

To decrypt the received message, use:

# receive encrypted_message from peer
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
res, message = @session.unwrap(message)
while res == Themis::SEND_AS_IS
  # send message to client
end

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

Send encrypted:

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

Receive and decrypt:

# receive encrypted_message from peer
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
   def finalize
end

Description:

  • initialize(shared_secret) - initialise secure comparator object with a shared_secret.

  • begin_compare - return a Secure Comparator initialisation message.

  • proceed_compare(control_message) - proceed with current and create the next comparation messages (when necessary).

  • result - return NOT_READY/MATCH/NOT_MATCH 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
@comparator = Themis::Scomparator.new('Test shared secret')

# this call defines the client part
comparation_message = @comparator.begin_compare

while @comparator.result == Themis::Scomparator::NOT_READY do
    user_send_function(comparation_message)
    comparation_message = user_receive_function()

    comparation_message = @comparator.proceed_compare(comparation_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
    comparation_message = user_receive_function()
    comparation_message = @comparator.proceed_compare(comparation_message)
    user_send_function(comparation_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.

Quickstart

Requirements

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

Installing Themis

Get Themis source code from GitHub

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

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

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

make install

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

To build and run tests, you can use:

make test

All the tests need to be passed with no errors.

Building Themis for C++

To install ThemisPP, type:

make themispp_install

To build and run exactly Themispp suit, you can:

make themispp_test test_spp

Using ThemisPP in your project

Insert

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

into your code.

Examples

Using Themis

Keypair generation

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

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

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

Keypair generation interface
enum asym_algs{EC, RSA};

template <asym_algs alg_t_p>
class themispp::secure_key_pair_generator_t {
     void gen();
     const std::vector<uint8_t>& get_priv();
     const std::vector<uint8_t>& get_pub();  
};

Description:

  • alg_t_p - template argument that sets the algorithm to be used. Value themispp::EC for Elliptic Curve or themispp::RSA for RSA
  • gen() - Generates key pair.
  • get_priv() - Returns generated private key.
  • get_pub() - Returns generated public key.
Example
// or themispp::secure_key_pair_generator_t<themispp::RSA> for RSA
themispp::secure_key_pair_generator_t<themispp::EC> g;

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

Secure Message

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

The Secure Message functions offer two modes of operation:

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

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

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

Secure Message interface
class themispp::secure_message_t {
     secure_message_t(const std::vector<uint8_t>& private_key, const std::vector<uint8_t>& peer_public_key);
     const std::vector<uint8_t>& encrypt(const std::vector<uint8_t>& data);
     const std::vector<uint8_t>& decrypt(const std::vector<uint8_t>& data);
     const std::vector<uint8_t>& sign(const std::vector<uint8_t>& data);
     const std::vector<uint8_t>& verify(const std::vector<uint8_t>& data);
};

Description:

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

Initialise encrypter:

themispp::secure_message_t b(private_key,peer_public_key);

Encrypt message:

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

Decrypt message:

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

Sign message:

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

Verify message:

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

Secure Cell

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

The general approach is that given:

  • input: some source data to protect;
  • key: a password;
  • context: plus an optional "context information";

Secure Cell functions will produce:

  • cell: the encrypted data;
  • authentication tag: some authentication data.

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

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

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

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

Secure Cell is available in 3 modes:

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

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

Secure Cell Seal mode
Secure Cell Seal mode interface
class themispp::secure_cell_seal_t {
    secure_cell_seal_t(const std::vector<uint8_t>& password);
    const std::vector<uint8_t>& encrypt(const std::vector<uint8_t>& data, const std::vector<uint8_t>& context);
    const std::vector<uint8_t>& encrypt(const std::vector<uint8_t>& data);
    const std::vector<uint8_t>& encrypt(const std::vector<uint8_t>& data, const std::vector<uint8_t>& context);
    const std::vector<uint8_t>& decrypt(const std::vector<uint8_t>& data);
};

Description:

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

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

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

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

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

Example

Initialise encrypter/decrypter

themispp::secure_cell_seal_t sm(password);

Encrypt

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

Decrypt

try {
     std::vector<uint8_t> decrypted_message = sm.decrypt(message, context);
   } catch(themispp::exception& e) {
     e.what();
   }
Secure Cell Token-protect Mode
Token-protect mode interface
class themispp::secure_cell_token_protect_t {
    secure_cell_token_protect_t(const std::vector<uint8_t>& password);
    const std::vector<uint8_t>& encrypt(const std::vector<uint8_t>& data, const std::vector<uint8_t>& context);
    const std::vector<uint8_t>& encrypt(const std::vector<uint8_t>& data);
    const std::vector<uint8_t>& encrypt(const std::vector<uint8_t>& data, const std::vector<uint8_t>& context);
    const std::vector<uint8_t>& decrypt(const std::vector<uint8_t>& data);
    const std::vector<uint8_t>& get_token();
    void set_token(const std::vector<uint8_t>& token);
};

Description:

  • secure_cell_token_protect_t(const std::vector<uint8_t>& password) - initialise scell token-protect mode object with password.

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

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

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

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

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

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

Example

Initialise encrypter/decrypter:

themispp::secure_cell_token_protect_t sm(password);

Encrypt

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

Decrypt

try {
      sm.set_token(token);  
      std::vector<uint8_t> decrypted_message = sm.decrypt(message, context);
   } catch(themispp::exception& e) {
      e.what();
   }
Secure Cell Context-Imprint Mode
Context-imprint mode interface
class themispp::secure_cell_context_imprint_t {
    secure_cell_context_imprint_t(const std::vector<uint8_t>& password);
    const std::vector<uint8_t>& encrypt(const std::vector<uint8_t>& data, const std::vector<uint8_t>& context);
    const std::vector<uint8_t>& encrypt(const std::vector<uint8_t>& data, const std::vector<uint8_t>& context);
};

Description:

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

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

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

Example

Initialise encrypter/decrypter

themispp::secure_cell_context_imprint_t sm(password);

Encrypt

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

Decrypt

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

Secure Session

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

Secure Session operates in two stages:

  • session negotiation where the keys are established and the cryptographic material is exchanged to generate ephemeral keys;
  • 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, secure_session_transport_interface_t* transport);
   const std::vector<uint8_t>& wrap(const std::vector<uint8_t>& data);
   const std::vector<uint8_t>& unwrap(const std::vector<uint8_t>& data);
   const std::vector<uint8_t>& init();
   bool is_established();
   void connect();
   const std::vector<uint8_t>& receive();
   void send(const std::vector<uint8_t>& data);
};
  • secure_session_t(const std::vector<uint8_t>& id, const std::vector<uint8_t>& private_key, secure_session_transport_interface_t* transport) - initialise Secure Session object with id, private_key and transport. Throws themispp::exception on failure.
  • const std::vector<uint8_t>& init() - return a Secure Session initialisation message. Throws themispp::exception on failure.
  • void connect() - create and send to peer a Secure Session initialisation message. Return nothing. Throws themispp::exception on failure.
  • const std::vector<uint8_t>& wrap(const std::vector<uint8_t>& data) - return wrapped message. Throws themispp::exception on failure.
  • void send(const std::vector<uint8_t>& data) - create wrapped message and send it to peer. Return nothing. Throws themispp::exception on failure.
  • const std::vector<uint8_t>& unwrap(const std::vector<uint8_t>& data) - return unwrapped message. If is_established() call return false the unwrapped message must be send to peer without any corrections. Throws themispp::exception on failure.
  • const std::vector<uint8_t>& receive() - return received from peer and decrypted plain message. Throws themispp::exception on failure.
Secure Session Workflow

Secure Session can be used in two ways:

  • send/receive - when communication flow is fully controlled by the Secure Session object.

  • wrap/unwrap - when communication is controlled by the user.

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

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

Send/Receive

Initialise callbacks class

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

First, initialise session:

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

// this call say that it is client
session.connect();

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

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

To send a message over established session, use:

session.send(message);

To receive the message from session:

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

First, initialise session:

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

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

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

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

Secure Session wrap/unwrap

Initialise callbacks class:

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

First, initialisation:

 pub_key_storage t;
 themispp::secure_session_t session(client_id, client_private_key, &t); //t - created transport callback object
 std::vector<uint8_t> message=session.init();
do {
   // send message to server
   // receive answer_message from server
   message=session.unwrap(answer_message);
} while(!session.is_established());

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

To encrypt the outgoing message, use:

std::vector<uint8_t> encrypted_message = session.wrap(message);
// send encrypted_message to peer by any preffered method

To decrypt the received message, use:

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

First, initialise everything:

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

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

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

Secure Comparator

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

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

Secure Comparator interface
class secure_comparator_t {
   secure_comparator_t(const data_t& shared_secret);
   const data_t& init();
   const data_t& proceed(const std::vector<uint8_t>& data);
   const bool get();
};

Description:

  • secure_comparator_t(const data_t& shared_secret) - initialise secure comparator object with a shared_secret.

  • init() - return a Secure Comparator initialisation message.

  • proceed(const std::vector<uint8_t>& data) - proceed with current and create the next comparation messages (when necessary).

  • get() - return true if result of comparison matches, false if doesn't.

Secure Comparator workflow

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

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

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

std::vector<uint8_t> buf;

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

// checks in a loop, imagine sending `buf` from client to server and back
buf = server.proceed(buf);
buf = client.proceed(buf);
buf = server.proceed(buf);
buf = client.proceed(buf);

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

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

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

Using Themis with Go

Introduction

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

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

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.

Quickstart

Getting gothemis

Requirements

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

Installing Themis

Get Themis source code from GitHub:

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

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

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

make install

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

To build and run basic tests, you can use:

make test

To build and run Go test suit, you can use:

make prepare_tests_all test_go

All the tests need to be passed with no errors.

Getting gothemis

If you have proper GoLang installation and $GOPATH set, just use go get to get gothemis and use it in your Go project:

    go get github.com/cossacklabs/themis/gothemis/...

Examples

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

You can generate keypairs like this:

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

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

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.

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 the sender supplying a valid public key of the receiver (encrypt/decrypt) or setting this parameter to NULL, or an empty string to use sign/verify.

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

Processing messages

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

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

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

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

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

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)

NOTE: You may use encryptor here to sign/verify messages as well, but not vice versa.

Secure Cell

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

The general approach is that given:

  • input: some source data to protect;
  • key: a password;
  • context: plus an optional "context information";

Secure Cell functions will produce:

  • cell: the encrypted data;
  • authentication tag: some authentication data.

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

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

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

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

Secure Cell is available in 3 modes:

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

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

Initialising Secure Cell

Create SecureCell object to protect your data:

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

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

// or
secureCell := cell.New(secretKey, cell.CELL_MODE_TOKEN_PROTECT)

// or
secureCell := cell.New(secretKey, cell.CELL_MODE_CONTEXT_IMPRINT)

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

Secure Cell Seal Mode

Initialise cell:

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

Encrypt:

// context is optional
protectedData, _, err := secureCell.Protect(data, context)

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

Decrypt:

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

// context is optional
data, err := secureCell.Unprotect(protectedData, nil, context)
Secure Cell Token-protect Mode

Initialise cell:

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

Encrypt:

// context is optional
protectedData, additionalData, err := secureCell.Protect(data, context)

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

Decrypt:

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

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

Initialise cell:

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

Encrypt:

// context required
protectedData, _, err := secureCell.Protect(data, context)

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

Decrypt:

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

// context required
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 bound to a specific session context.

Secure Session operates in two stages:

  • session negotiation where the keys are established and the cryptographic material is exchanged to generate ephemeral keys;
  • 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 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).

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 := getPublicKeyFromDatabaseOrOtherStorageOrSource(id)

    return pub /* or nil */
}

func (clb *callbacks) StateChanged(ss *session.SecureSession, state int) {
    // do something
}

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 contained encrypted data from your peer, try to decrypt it
data, sendPeer, err := session.Unwrap(receiveBuffer)
// check err

if sendPeer {
    // receiveBuffer was part of the negotiation protocol,
    // so data contains the response to this protocol,
    // which needs to be forwarded to your peer.

    // just send data to your peer
} else {
    // data may be nil on the client when the Secure Session completes the negotiation
    if data != nil {
        // now data contains decrypted data

        // process data according to your application
    }
}

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

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

Please refer to 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) // byte[]

// Initiating secure compare (client, step1)
buf, err := scomparator.Begin()

for {
    // check comparison result
    res, err := scomparator.Result()

    if compare.COMPARE_NOT_READY == res {
        // send `buf` on server and receive `readed_bytes`

        // proceed and send again
        buffer, err := scomparator.Proceed(buf[:readed_bytes])
    }  else {
        if compare.COMPARE_MATCH == res {
            fmt.Println("match")
        } else {
            fmt.Println("not match")
        }
        break
    }
}

After the loop finishes, the comparison is over and its result is checked calling sc.Result().

Secure Comparator server

Server part can be described in any language, let's pretend 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[]

for {
    // check comparison result
    res, err := scomparator.Result()

    if compare.COMPARE_NOT_READY == res {
        // receive `readed_bytes` from client

        // proceed and send to client
        buffer, err := scomparator.Proceed(buf[:readed_bytes])
    }  else {
        if compare.COMPARE_MATCH == res {
            fmt.Println("match")
        } else {
            fmt.Println("not match")
        }
        break
    }
}

After the loop finishes, the comparison is over and its result is checked calling sc.Result().

Please refer to the Secure Comparator Examples to get the complete vision 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: 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.

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.

Supported versions

jsthemis v0.10.1 is tested and supported on Node.js v8-v10. It depends on nan > 2.8.0 package.

If you are looking for compatibility with older Node.js or nan, please see older jsthemis versions.

Quickstart

Requirements

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

Installing stable version from packages

  1. Install OpenSSL, LibreSSL or BoringSSL as system libraries.

  2. Install Themis as system library using your system's package manager.

  3. Install jsthemis wrapper from npm.

npm install jsthemis

Installing laster version from master branch

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

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

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

make install

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

To build and run tests, you can use:

make test

To install, build and run jsthemis test suit, you can use:

make jsthemis_install && make prepare_tests_all test_js

All the tests need to be passed with no errors.

Examples

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

Using Themis

Key Pair Generation

Private/public 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
function KeyPair(alg);

Description:

  • KeyPair() - return private key and public key strings for Elliptic Curve algorithm. Raises ThemisError on failure.

  • KeyPair("rsa") - return private key and public key strings for RSA algorithm. Raises ThemisError on failure.

Example
// OR themis.KeyPair("rsa")
key_pair = new themis.KeyPair(); 
private_key = key_pair.private().toString("base64");
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 the sender supplying a valid public key of the receiver (encrypt/decrypt) or setting this parameter to NULL, or an empty string to use sign/verify.

Read more about 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:

  • SecureMessage(private_key, peer_public_key) - initialise Secure Message object with private_key and peer_public_key. Raises Error on failure.

  • encrypt(message) - encrypt message. Return encrypted secure message container as Buffer. Raises Error on failure.

  • decrypt(message) - decrypt message. Return decrypted message as Buffer. Raises Error on failure.

  • sign(message) - Sign message. Return signed Secure Message container as Buffer. Raises Error on failure.

  • verify(message) - Verify Buffer contained signed Secure Message container. Return Buffer with plain message. Raises Error on failure.

Example

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

Initialise encrypter:

smessage = themis.SecureMessage(private_key, peer_public_key)

// or

smessage = themis.SecureMessage(new Buffer(private_key, "base64"), new Buffer(peer_public_key, "base64"));

Encrypt message:

try {
   encrypted_message = smessage.encrypt(message)
} catch(error_var) {
   // error occurred
}

Decrypt message:

try {
   decrypted_message = smessage.decrypt(encrypted_message)
} catch(error_var) {
   // error occurred
}

Sign message:

try {
   signed_message = smessage.sign(message)
} catch(error_var) {
   // error occurred
}

Verify message:

try {
   message = smessage.verify(signed_message)
} catch(error_var) {
   // error occurred
}

Secure Cell

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

The general approach is that given:

  • input: some source data to protect;
  • key: a password;
  • context: plus an optional "context information";

Secure Cell functions will produce:

  • cell: the encrypted data;
  • authentication tag: some authentication data.

The purpose of the optional "context information" (i.e. a database row number or 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, then decryption will fail.

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

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

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

Secure Cell is available in 3 modes:

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

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

Secure Cell interface:
function SecureCellSeal(key) {
   function encrypt(message, context);
   function decrypt(message, context);
}

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

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

Description:

  • SecureCellSeal(key) - initialise Secure Cell Seal mode object with key.
  • encrypt(message, context) - encrypt message with optional context. Return encrypted Buffer. Raises Error on failure.
  • decrypt(message, context) - decrypt message with optional context. Return decrypted Buffer. Raises Error on failure.
  • SecureCellTokenProtect(key) - initialise Secure Cell Token Protect mode object with key.
  • encrypt(message, context) - encrypt message with optional context. Return array of two Buffers: encrypted message and token. Raises Error on failure.
  • decrypt(message, token, context) - decrypt message with token and optional context. Return decrypted Buffer. Raises Error on failure.
  • SecureCellContextImprint(key) - initialise Secure Cell Context Imprint mode object with key.
  • encrypt(message, context) - encrypt message with context. Return encrypted Buffer. Raises Error on failure.
  • decrypt(message, context) - decrypt message with context. Return decrypted Buffer. Raises Error on failure.

Examples

Secure Cell Seal Mode

Initialise encrypter/decrypter

scell_seal = new themis.SecureCellSeal(key);

Encrypt

// context is optional
encrypted_message = scell_seal.encrypt(message, context)

Decrypt

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

decrypted_message = scell_seal.decrypt(encrypted_message, context)
Secure Cell Token-protect Mode

Initialise encrypter/decrypter

scell_token_protect = new SecureCellTokenProtect(key);

Encrypt

// context is optional
encrypted_array  = scell_token_protect.encrypt(message, context);

Decrypt

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

decrypted_message = scell_token_protect.decrypt(encrypted_array.data, encrypted_array.token, context);
Secure Cell Context-Imprint Mode

Initialise encrypter/decrypter

scell_context_imprint = new themis.SecureCellContextImprint(key);

Encrypt

encrypted_message = scell_context_imprint.encrypt(message, context);

Decrypt

decrypted_message = scell_context_imprint.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 bound to a specific session context.

Secure Session operates in two stages:

  • session negotiation where the keys are established and the cryptographic material is exchanged to generate ephemeral keys;
  • 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(peer_id) {
   return pub_key_by_id;
}) {
  function connectRequest();
  function wrap(message);
  function unwrap(message);
  function isEstablished();
}

Description:

  • SecureSession(id, private_key, get_pub_key_by_id function(peer_id){}) - initialise Secure Session object with id, private_key and a get_pub_key_by_id function(peer_id) - function that gets Buffer with peer identifier and returns public key for it. Raises Error on failure.

  • connect_request() - return connection initialisation message. Raises Error on failure.

  • wrap(message) - return ready for sending encrypted message. Raises Error on failure.
  • unwrap(message) - return unwrapped message. Raises Error on failure.
Secure Session Workflow

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

Secure Session client

First, initialisation:

client_session = new addon.SecureSession(client_id, client_keypair.private(), function(id) {
   // get public key for specified id from file, database, etc.
   return public_key;
});
data = client_session.connectRequest()
do {
   // send data to peer
   // receive response from peer
   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:

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

To decrypt the received message, use:

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

First, initialise everything.

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

while(!client_session.isEstablished()) {
   // receive response from peer
   data = client_session.unwrap(response);
   // send data to peer
}

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 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 themis = require('jsthemis');

var client = new themis.SecureComparator(new Buffer("shared secret"));
var data = client.beginCompare()

while (!client.isCompareComplete()) {
    // send data on server and receive response
    sendDataOnServer(data);
    data = receiveFromServer();

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

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

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

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

var themis = require('jsthemis');

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

while (!server.isCompareComplete()) {
    // receive from client
    data = receiveFromClient();

    // proceed and send again
    data = server.proceedCompare(data);
}

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

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

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

Migration guide - Themis 0.9.6 to Themis 0.10.0

This is a migration guide of moving from Themis 0.9.6 to Themis 0.10.0.

Secure Cell compatibility

In 0.10.0 version, we fixed the issue of incompatibility (#279) between x32 and x64 platforms when using Secure Cell. It was a core issue, which applied to every language wrapper. In short, the plaintext encrypted on x32 platforms couldn't be decrypted on x64 platforms and vice versa.

Starting with Themis 0.10.0, we can confirm the full compatibility (can be encrypted and decrypted both ways) for:

0.10.0 х64 <-> 0.10.0 х32
0.10.0 х64 <-> 0.9.6 х32
0.10.0 х32 <-> 0.9.6 х32

Mostly compatible:
0.10.0 x64 <-> 0.9.6 x64 (the almost-completeness of compatibility affects the SecureCell Context Imprint only; see below).

Incompatible:
0.9.6 x64 can't decrypt messages encrypted by 0.10.0 x64/x32. The users of 0.9.6 should update to 0.10.0.
0.10.0 x32 can't decrypt messages encrypted by 0.9.6 x64. The users of 0.9.6 should update to 0.10.0.

We recommend everyone to update their version of Themis to 0.10.0 for every platform/language wrapper.

Note: In the worst case scenario, we suggest that you decrypt and backup your data before updating to prevent data loss. Encrypt the data after performing the update again. This might be an excessive precaution as we haven't received any comments about data loss from the users of older versions of Themis, but it's always better to stay on the safe side.

If you have any questions or encounter some difficulties during the migration process, don't hesitate to send us an email to dev@cossacklabs.com, we can help.

Context Imprint

This mode doesn't calculate an authentication tag, which means that integrity checks can't be done, so if decryption is not possible, corrupted data is returned, no error is raised. Please see the dedicated Secure Cell cryptosystem page for the details.

RubyThemis gem rename

Since 0.10.0 we have renamed gem to rbthemis. If you were previously using an older version of rubythemis gem, please uninstall it from your system using gem uninstall rubythemis before installing rbthemis. This change affects only public gem name, function names and imports remains the same.

RubyThemis Secure Cell in Token Protect mode

In Secure Cell Token Protect mode, Themis puts the authentication tag and other auxiliary information to a separate buffer (aka additional_data), so the user can store it elsewhere while keeping the original encrypted data size. The same token has to be provided along with the correct secret for the data to be decrypted successfully.

scell_auto_split = Themis::Scell.new(key, Themis::Scell::TOKEN_PROTECT_MODE)
encrypted_message, additional_auth_data = scell_auto_split.encrypt(message, context)
decrypted_message = scell_auto_split.decrypt([encrypted_message, additional_auth_data], context)

The encrypt and decrypt functions operate with an array of two parameters [encrypted_message, additional_auth_data].

The former issue (#281) in rubythemis caused the parameters reverse in a wrong way during the encryption and decryption. Rubythemis users won't notice any difference, but it was impossible to decrypt the results of such encryption on other language platforms.

We fixed that issue in 0.10.0. So if you are using this mode and get decryption errors, we suggest reversing the decryption parameters for the messages encrypted using previous versions of rubythemis.

For example:

decrypted_message = scell_auto_split.decrypt([encrypted_message, additional_auth_data], context)
# if previous fails
decrypted_message = scell_auto_split.decrypt([additional_auth_data, encrypted_message], context)

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.