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

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 cryptographic material is exchanged to generate ephemeral keys and
  • data exchange the where exchanging of messages can be carried out between peers.

You can read a more detailed description of the process.

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");
}