Building secure chat with Themis and MWE

Introduction

Imagine you'd like to build your own chat server, which allows clients to exchange messages safely. You have a simple infrastructure consisting of server written in Ruby and clients for iOS and Android. This is exactly what the famous Mobile websocket example provides. We have modified it to illustrate how simple adding security features with Themis is.

In this tutorial, we'll try to preserve as much of it's simplicity and architecture as possible, but add cryptographic protection. In case you've randomly bumped into this blog post, Themis is a cross-platform cryptographic framework, providing convenient services to easily store and exchange data over the network. You can read more here.

[TL;DR] Quick jump between sections: Architecture, Ruby server, Android client, iOS client

## Architecture overview

Mobile websocket example (MWE) regular workflow consists of two parts:

Sign-in process:

A server listens to websocket, once server receives new connection, it subscribes new user to main chat channel and informs everybody that a new user joins:

Message exchange:

For each client, server keeps separate event loop in memory, listening to events from this specific client. When somebody pushes something to a server over their connection, server relays that message to everyone via channel everybody is subscribed to.

Adding Themis into scheme

The plan:

  1. Add names for users to identify each other (and for server to identify their keys).
  2. Add Secure Session between device and server to provide protection.
  3. Add Secure Cell storage on mobile devices to protect stored data.

Secure Session is a lightweight session-based communication protection scheme. It is protocol agnostic and operates on the 5th layer of the network OSI model. Secure Session provides:

  • secure end-to-end communication
  • perfect forward secrecy
  • strong mutual peer authentication
  • replay protection
  • low negotiation round-trip
  • uses strong cryptography (including ECC)

Communications over Secure Session contains 2 stages: negotiation (key agreement) stage and actual data exchange. In our example, client and server will use hardcoded server keys (private and public correspondingly) to start the session. Depending on language you use, pick 'key pair generation' section from language reference of Themis documentation

Secure Cell is simple protected data container, aimed to protect arbitrary data being stored in various types of storages (like databases, filesystem files, document archives, cloud storage etc). It's rather simple, but includes strong cryptographic protection and data authentication mechanisms.

After dropping Themis, sign-in process will look like this:

Client "newcomer" will perform key agreement procedure and session establishment procedure with server over websocket, resulting in generation of temporary key, which will be used by this client and server to exchange data. This temporary key is bound to session identifier, which will allow the server to pick the right key for each session with each user (more on that in Ruby server section)

Message exchange will look like this:

Every message, which server is able to decrypt (e.g., for which server has active session) is decrypted with session key, then encrypted with each session key available to the system and sent to all clients.

Warning: This architecture is used only to show how simple integrating Themis into your application actually is. We preserve as much of initial architecture and process flow as possible. This, however, opens a few problems:

  • Bandwidth degradation: for N users, every message will be sent N*N times instead on N times (because we send each encrypted message to the same channel and everybody will receive them), so traffic footprint will be N times more than normal.
  • Attack on session keys: since client knows the message he sent, by getting it encrypted with every other user's key from server, he is able to try "known plaintext attack" (e.g. try to retrieve keys from encrypted text and known plaintext message). Modern ciphers used in Secure Session are safe against this attack, and overall security in our case good. But in general, such architectural patterns introduce risks and should be avoided.

Using the tutorial code

Since we like tutorials which really work, the whole infrastructure is ready to run on localhost. In case you'd like more real-world testing with addresses, which face the internet, you need to change them both in application code and in ruby server code, and make sure addresses and ports are available in the system you're running server code on.

## Ruby

Modified Ruby code to see the result of this section is available at corresponding directory in GitHub repo.

Building Ruby Server

First, we will build Ruby server, then add Themis services into it. To do that, we'll install Ruby and Themis (we're using clean Ubuntu installation within typical VM):

apt-get install build-essentials ruby ruby-dev libssl-dev
gem install em-websocket
gem install rubythemis

Since Ruby is rapidly changing ecosystem, if your rubythemis installation fails for any reason, just refer to current version of ruby guide.

Let's see how the server works in original Mobile websocket example:

require "em-websocket"
EventMachine.run do
   @channel = EM::Channel.new
   EventMachine::WebSocket.start(host: "0.0.0.0", port: 8080, debug: true) do |ws|
       ws.onopen do
           sid = @channel.subscribe { |msg| ws.send(msg) }
           @channel.push("#{sid} connected")
           ws.onmessage { |msg| @channel.push("<#{sid}> #{msg}") }
           ws.onclose { @channel.unsubscribe(sid) }
       end
   end
end

For each incoming user, we:

  1. subscribe remote user to websocket channel via @channel.subscribe
  2. notify everyone that a new user is present
  3. create event listener, which reacts to incoming messages over websocket by pushing them into channel
  4. when user closes the websocket, we detach him from channel.

 

This provides basic chat server experience in our case.

 

Adding Themis cryptography to Ruby server

Now, let's add all the components necessary to provide encrypted Secure Session services within this chat server:

  1. add dependencies
  2. add server key
  3. create infrastructure for storing the session keys: for every new session we create ephemeral keys, which are valid only for this session. since the server will handle a lot of sessions simultaneously, Themis library needs some way to know which key to use with current session. Callback is simple and appropriate pattern here.
  4. instead of just receiving the message and pushing it to everyone, we use Secure Session's state to control flow after receiving any message via websocket - each message can be treated as:
    • signal to initiate new session: user sends his key and id, we store it and consider this as user's signal to start key acknowledgment.
    • ongoing session initiation traffic (key negotiation takes a few steps, which Secure Session object performs itself)
    • when session is established, message is expected to contain Secure Session message within it, which decrypts to regular text.

First, we'll add dependences (we'll need base64 to send binary data in strings):

require "rubythemis"
require "base64"

Then, we need to add pre-shared server private key:


server_priv_key = "\x52\x45\x43\x32\x00\x00\x00\x2d\x49\x87\x04\x6b\x00\xf2\x06\x07\x7d\xc7\x1c\x59\xa1\x8f\x39\xfc\x94\x81\x3f\x9e\xc5\xba\x70\x6f\x93\x08\x8d\xe3\x85\x82\x5b\xf8\x3f\xc6\x9f\x0b\xdf"

We'll need two arrays - one for keys and one for sessions, and a callback method to return user's key according to it's id. Callback is required by Secure Session architecture to be able to access keys while establishing the session:

$pub_keys = Hash.new
$sessions = Hash.new
callback method 

class Callbacks_for_themis < Themis::Callbacks
    def get_pub_key_by_id(id)
        return $pub_keys[id].force_encoding("BINARY")
    end
end

Then we start EventMachine with websocket listener:

EventMachine.run do
  @channel = EM::Channel.new

  EventMachine::WebSocket.start(host: "0.0.0.0", port: 8080, debug: true) do |ws|
    ws.onopen do

Secure Session is established via sequence of stages, and in each moment in time can be in three states:

  • none: (stage 0) session between two parties is not present
  • initiating: (stage !0, result = 1) session is being initiated right now
  • initiated: (stage !0, result > 1) session is initiated and ready to use

Based on session stage, we treat incoming messages in a different manner:

  • case 1: if stage is 0, we create new session identifier and allocate variables, callbacks and various tooling
  • case 2: if stage is not 0, we try to decrypt a message and based on result we:
    • case 2-1: if result of decrypting is 1, we're still in progress of session initiation, so we just have to carry on with it
    • case 2-2: if result is > 1 and stage is not 0, this is a sign of established session and we can execute normal flow: for each session, encrypt message with it's session key and push into main channel.

Case 1: if Stage is 0:

id_pubkey = msg.split(":")
id=id_pubkey[0]
$pub_keys[id_pubkey[0]]=Base64.decode64(id_pubkey[1])
callbacks = Callbacks_for_themis.new
    ssession = Themis::Ssession.new("server", server_priv_key, callbacks)
    $sessions[id]=ssession
stage+=1

Case 2: Stage is not 0: try to decrypt a message and retrieve control information from Themis:

res, mes = $sessions[id].unwrap(Base64.decode64(msg))    # Trying to decrypt message

Case 2-1: Is decryption result 1?: if it's 1, carry on establishing the session


if res == 1 #if res equals Themis::SEND_AS_IS, we're still initiating the session
ws.send(Base64.encode64(mes))

Case 2-2: If decryption result is not 1: it means session is established and we can get messages from client, decrypt them and forward to other users, encrypting it with each user's keys. This way, we send channel-wide messages only when we have message coming over established session:

$sessions.each do |sid, session|
    @channel.push(Base64.encode64($sessions[id].wrap(mes)))

Voila! We've replaced handling of incoming messages by pushing them to the channel by number of choices, covering all possible states of Secure Session.

When client unbinds, we delete session data from sessions hash and delete public key.

      ws.onclose { @channel.unsubscribe(sid) 
		   $sessions.delete(id)
		   $pub_keys.delete(id)

That's it! By replacing message forwarding handler with simple control flow, you now have Secure Session protecting your messages for you.

But what about the clients? Since it's yet impossible to run Themis in your web browser (however, this will change sooner than you would have imagined), our next targets will be two mobile platforms.

## Android

Setting up development environment

Once again, we're using clean Ubuntu install.

If you follow google developer instructions properly, everything will go smoothly. However one thing to keep in mind - if you have a fresh default Ubuntu machine (tested on 14.04 LTS) you should install zlib1g apart from JDK described in Android docs:

sudo apt-get install zlib1g

Or on x64:

sudo apt-get install zlib1g:i386

If you do not do this and try to run any build from Android Studio, it will freeze forever without giving you any errors or reasons.

Building Themis for Android

By following Themis installation guide, you should get working Themis installation.

However, if Themis progresses far enough from time this tutorial is being written, you might want to use it in state, in which this tutorial was written with:

git clone https://github.com/cossacklabs/themis 
cd themis
git reset --hard b6e8e4d479d2fa6118423059021d864f1ef763e2

Adding Themis to chat app

The Android client for mobile websocket example (MWE) is easily opened and built by recent Android Studio.

First, we've added a reference to Themis Android library to the client. There is one slight 'hack', which is MWE's AndroidManifest.xml: the MWE client declares minimal suported SDK version as 14 (Android 4.0), but Themis prefers 16 (Android 4.1), so we need to update the MWE client to comply. Why bother trying to add security for old buggy and breachy Android releases in the first place?

So, build.gradle:

minSdkVersion 14 -> minSdkVersion 16

And AndroidManifest.xml:

android:minSdkVersion="14" -> android:minSdkVersion="16"

The plan:

  1. Add Themis infrastructure into the app, generate keys on startup
  2. Patch all client-server functions to talk through Secure Session
  3. Extend the example to save message history between app cycle securely with Secure Cell

And to the fun part - MainActivity.java:

Importing Themis functions:

import com.cossacklabs.themis.ISessionCallbacks;
import com.cossacklabs.themis.InvalidArgumentException;
import com.cossacklabs.themis.KeyGenerationException;
import com.cossacklabs.themis.KeypairGenerator;
import com.cossacklabs.themis.Keypair;
import com.cossacklabs.themis.NullArgumentException;
import com.cossacklabs.themis.PublicKey;
import com.cossacklabs.themis.SecureCellData;
import com.cossacklabs.themis.SecureCellException;
import com.cossacklabs.themis.SecureSession;
import com.cossacklabs.themis.SecureSessionException;
import com.cossacklabs.themis.SecureCell;

Adding some new private members to the activity:


// This will be our keypair to communicate with the server
private Keypair keypair;

// Secure session for our WebSocket
private SecureSession secureSession;

// DeviceID: we will use it as a key to protect message history (example only, do not use in production)
private final String deviceId = Build.MANUFACTURER + Build.MODEL;

Each time application starts, we'll generate new keypair:

@Override
protected void onCreate(Bundle savedInstanceState) {
    ...
    keypair = KeypairGenerator.generateKeypair();
    ...
}

Establish Secure Session with server

To create Secure Session, when connecting through WebSocket, we do the following:

private void connectWebSocket() {
    ...
                secureSession = new SecureSession(
                    deviceId.getBytes("UTF-8"),
                    keypair.getPrivateKey(),
                    new ISessionCallbacks() {
                        @Override
                        public PublicKey getPublicKeyForId(SecureSession secureSession, byte[] bytes) {
                            // Assuming we speak to one server only, just returning his "known" private key
                            return new PublicKey(new byte[]{0x55, 0x45, 0x43, 0x32, 0x00, 0x00, 0x00, 0x2d, 0x75, 0x58, 0x33, (byte)0xd4, 0x02, 0x12, (byte)0xdf, 0x1f, (byte)0xe9, (byte)0xea, 0x48, 0x11, (byte)0xe1, (byte)0xf9, 0x71, (byte)0x8e, 0x24, 0x11, (byte)0xcb, (byte)0xfd, (byte)0xc0, (byte)0xa3, 0x6e, (byte)0xd6, (byte)0xac, (byte)0x88, (byte)0xb6, 0x44, (byte)0xc2, (byte)0x9a, 0x24, (byte)0x84, (byte)0xee, 0x50, 0x4c, 0x3e, (byte)0xa0});
                        }

                        @Override
                        public void stateChanged(final SecureSession secureSession) {
                            // Let's track secure session establishment by adding state notifications to overall message history
                            runOnUiThread(new Runnable() {
                                @Override
                                public void run() {
                                    TextView textView = (TextView)findViewById(R.id.messages);
                                    textView.setText(textView.getText() + "\n" + secureSession.getState().name());
                                }
                            });
                        }
                    }
            );
    ...
}

Sending the key to server

For the server to know our public key, we need to get introduced first, so we need send it to the server first (see scheme 1 in 'Adding Themis to the scheme'):

Instead of basic insecure "Hello"

:

@Override
public void onOpen(ServerHandshake serverHandshake) {
    ...
    mWebSocketClient.send("Hello from " + Build.MANUFACTURER + " " + Build.MODEL);
    ...
}

We will send our id + public key:


@Override
public void onOpen(ServerHandshake serverHandshake) {
    ...
    mWebSocketClient.send(deviceId + ":" + Base64.encodeToString(keypair.getPublicKey().toByteArray(), Base64.DEFAULT));
    mWebSocketClient.send(Base64.encodeToString(secureSession.generateConnectRequest(), Base64.DEFAULT));
    ...
}

Receiving protected messages via Secure Session

As for received messages, instead of regular handler, which just displays incoming messages:


@Override
public void onMessage(String s) {
    final String message = s;
     runOnUiThread(new Runnable() {
        @Override
          public void run() {
          TextView textView = (TextView)findViewById(R.id.messages);
          textView.setText(textView.getText() + "\n" + message);
        }
        });
    }

We'll do Secure Session state-based flow:


@Override
public void onMessage(String s) {

     byte[] wrappedData = Base64.decode(s, Base64.DEFAULT);

     SecureSession.UnwrapResult unwrapResult = secureSession.unwrap(wrappedData);

     switch (unwrapResult.getDataType()) {
            case PROTOCOL_DATA:
               // The session is not established yet. Send response to the server.
                  mWebSocketClient.send(Base64.encodeToString(unwrapResult.getData(), Base64.DEFAULT));
                  break;
            case NO_DATA:
               // Nothing to do... Nothing to send...
                  break;
            case USER_DATA:
               // This is our received message. Display it to the user.
                  final String message = new String(unwrapResult.getData(), "UTF-8");
                  runOnUiThread(new Runnable() {
                    @Override
                        public void run() {
                              TextView textView = (TextView)findViewById(R.id.messages);
                              textView.setText(textView.getText() + "\n" + message);
                         }
                    });
                  break;
               }
          }

Sending protected messages via Secure Session

Initial code for sending messages looked like this:

public void sendMessage(View view) {
    EditText editText = (EditText)findViewById(R.id.message);

    mWebSocketClient.send(editText.getText().toString());

    editText.setText("");
}

We will replace it with encryption part:

public void sendMessage(View view) {
    EditText editText = (EditText)findViewById(R.id.message);

    byte[] wrapped = secureSession.wrap(editText.getText().toString().getBytes("UTF-8"));
    mWebSocketClient.send(Base64.encodeToString(wrapped, Base64.DEFAULT));

    editText.setText("");
}

Adding secure storage for message history

Purely for illustrational purposes, we've added local protected storage for anything you'd like to keep safe within your app. In this case, it's history.

It's rather simplistic:

  • on application startup we try to locate object 'history' in SharedPrefs, and if it's there - expect it to be Secure Cell object, decrypt it and show in text window.
  • on application shutdown we just record state of textView into variable, which is then encrypted into SecureCell and put into SharedPrefs.
@Override
protected void onStart()
    {
    super.onStart();

     TextView textView = (TextView)findViewById(R.id.messages);
     SharedPreferences sharedPref = getPreferences(Context.MODE_PRIVATE);

     if (sharedPref.contains("history")) {
        // DeviceID is used here just for simplicity of the example. In real-world it should be a "real" password. For example, just prompt the user to enter his storage password when app starts.
          SecureCell secureCell = new SecureCell(deviceId);
          SecureCellData protectedData = new SecureCellData(Base64.decode(sharedPref.getString("history", ""), Base64.DEFAULT), null);
          textView.setText(new String(secureCell.unprotect(deviceId.getBytes("UTF-8"), protectedData), "UTF-8"));
    }
}

@Override
protected void onStop() {
     TextView textView = (TextView)findViewById(R.id.messages);

     SharedPreferences sharedPref = getPreferences(Context.MODE_PRIVATE);
     SharedPreferences.Editor editor = sharedPref.edit();

     SecureCell secureCell = new SecureCell(deviceId);
     SecureCellData protectedData = secureCell.protect(deviceId.getBytes("UTF-8"), textView.getText().toString().getBytes("UTF-8"));

     editor.putString("history", Base64.encodeToString(protectedData.getProtectedData(), Base64.DEFAULT));
     editor.commit();

     super.onStop();
}

## iOS

Building and running

To build iOS application you need OSX 10.9+ and Xcode. You can download it from AppStore. Example project was built on Xcode6 using iOS8 SDK, but updated it to support Xcode7 and iOS9 SDK too.

To build application, please follow the guide. In few words, you need to install CocoaPods, then run ruby server and the run application itself.

The plan

Websocket example application provides simple chatting with Ruby server above. We will extend MWE's iOS client to work with Themis's Secure Session and Secure Cell.

In order to use Themis, we will:

  • 1. Add Themis lib into project.
  • 2. Generate keys when application starts.
  • 3. Wrap client-server interactions with SecureSession.
  • 4. Store message history with SecureCell.

Adding Themis

If you inspect Podfile, you will definitely find there two pods: SocketRocket and latest Themis.

You can link Themis on head of master branch, on specific commit or on latest release:

`pod "themis", :git => 'https://github.com/cossacklabs/themis.git', :commit => 'b6e8e4d479d2fa6118423059021d864f1ef763e2'`

If you prefer not to use CocoaPods, just drag sources into your project and add `OpenSSL-Universal` lib.

Initialize keys

Server public key is hardcoded into the app. Usually, hardcoding keys is a bad practice, but good enough for demonstration purposes.

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

Next, we need to generate private and public key:

TSKeyGen * keygenEC = [[TSKeyGen alloc] initWithAlgorithm:TSKeyGenAsymmetricAlgorithmEC];
if (!keygenEC) {
    [self loggingEvent:@"Error while initializing keygen"];
    return;
}

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

Connecting to server

In order to connect with localhost server, you need to run ruby server from the terminal. If server is not running yet, please, follow guide mentioned above :)

- (void)connectWebSocket {
    NSString * urlString = @"ws://127.0.0.1:8080";
    self.webSocket = [[SRWebSocket alloc] initWithURL:[NSURL URLWithString:urlString]];
    self.webSocket.delegate = self;

    [self.webSocket open];
}

Sending key to server, aka handshake messages

As previously agreed, first message from client to server is handshake in format `device name:public key'.

NSString * name = [UIDevice currentDevice].name;
NSString * handshakeMessage = [NSString stringWithFormat:@"%@:%@", name, [publicKey base64EncodedStringWithOptions:0]];
[self.webSocket send:handshakeMessage];

Establishing the session

Establish session, initialize Transport object and TSSession object using client's private key.

    // send establishment message
    self.transport = [Transport new];
    self.session = [[TSSession alloc] initWithUserId:[name dataUsingEncoding:NSUTF8StringEncoding]
                                          privateKey:privateKey
                                           callbacks:self.transport];
    NSError * error = nil;
    NSData * sessionEstablishingData = [self.session connectRequest:&error];
    if (error) {
        [self loggingEvent:[NSString stringWithFormat:@"Error while handshake %@", error]];
        return;
    }

    NSString * sessionEstablishingString = [sessionEstablishingData base64EncodedStringWithOptions:0];
    [self.webSocket send:sessionEstablishingString];

If session is not established yet, client unwraps response from server and sends it back:

- (void)webSocket:(SRWebSocket *)webSocket didReceiveMessage:(id)message {
    ...
    NSData * receivedData = [[NSData alloc] initWithBase64EncodedString:message options:NSDataBase64DecodingIgnoreUnknownCharacters];
    NSData * unwrappedMessage = [self.session unwrapData:receivedData error:&error];

    if (![self.session isSessionEstablished] && unwrappedMessage) {
        NSString * unwrappedStringMessage = [unwrappedMessage base64EncodedStringWithOptions:0];
        [webSocket send:unwrappedStringMessage];
        return;
    }
    ...
}

Sending protected user messages via SecureSession

After these simple initialization steps are done, it's time to send user's messages to server. But first we should wrap and encode each message to NSData, and send it as base64 NSString.

- (IBAction)sendMessage:(id)sender {
    NSData * dataToSend = [self.messageTextField.text dataUsingEncoding:NSUTF8StringEncoding];

    // wrap data
    NSError * error;
    NSData * wrappedData = [self.session wrapData:dataToSend error:&error];

    if (!wrappedData || error) {
        [self loggingEvent:[NSString stringWithFormat:@"Error on wrapping message %@", error]];
        return;
    }

    NSString * wrappedStringMessage = [wrappedData base64EncodedStringWithOptions:0];
    [self.webSocket send:wrappedStringMessage];
}

Receiving protected server messages via SecureSession

Protected messages are encoded, so it's not enough just to read them from socket. First, message is unwraped to NSData, checked on errors, then transformed into NSString.

- (void)webSocket:(SRWebSocket *)webSocket didReceiveMessage:(id)message {
     ...
    NSData * receivedData = [[NSData alloc] initWithBase64EncodedString:message options:NSDataBase64DecodingIgnoreUnknownCharacters];
    NSData * unwrappedMessage = [self.session unwrapData:receivedData error:&error];
    ...
    NSString * unwrappedString = [[NSString alloc] initWithData:unwrappedMessage encoding:NSUTF8StringEncoding];
}

Storing history

Storing chat history may be valuable feature for users, and is a nice use-case to illustrate protected local storage.

The flow is trivial:

  1. Create SecureCell object that handles encrypting/decrypting messages.
  2. Encrypt and put every message to storage.
  3. Read history and decrypt messages on application start.

Let's create SecureCell object and save it to `self.secureStorageEnryptor` property.

- (void)createSecureStorage {
    // create encryptor if there's no
    if (!self.secureStorageEnryptor) {

        // you should NEVER use uuid as encryption key ;)
        NSString * encryptionKey = [[[UIDevice currentDevice] identifierForVendor] UUIDString];
        self.secureStorageEnryptor = [[TSCellSeal alloc] initWithKey:[encryptionKey dataUsingEncoding:NSUTF8StringEncoding]];
    }
}

Put each message to history when user sends message or receives server's response. Ask SecureCell to encrypt messages and store them in NSUserDefaults.

- (void)saveHistory:(NSString *)string {
    // create encryptor if there's no
    [self createSecureStorage];

    // encrypt event
    NSError * error = nil;
    NSString * message = [NSString stringWithFormat:@"%@ %@", [NSDate date], string];
    NSData * encryptedEvent = [self.secureStorageEnryptor wrapData:[message dataUsingEncoding:NSUTF8StringEncoding]
                                                           context:nil
                                                             error:&error];
    if (!encryptedEvent || error) {
        NSLog(@"Error on encrypting message %@", error);
        return;
    }

    NSUserDefaults * userDefaults = [NSUserDefaults standardUserDefaults];

    // check if history is already presented
    NSMutableArray * history = [[userDefaults valueForKey:kHistoryStoringKey] mutableCopy];
    if (!history) {
        history = [NSMutableArray new];
    }

    // add encrypted object to history
    [history addObject:encryptedEvent];

    // add history to storage
    [userDefaults setObject:history forKey:kHistoryStoringKey];
}

Reading history data, decrypting every message.

- (void)readHistory {
    NSLog(@"Previous message history on this client...");
    [self createSecureStorage];

    NSUserDefaults * userDefaults = [NSUserDefaults standardUserDefaults];
    NSMutableArray * history = [userDefaults valueForKey:kHistoryStoringKey];

    [history enumerateObjectsUsingBlock:^(NSData * encryptedData, NSUInteger idx, BOOL * stop) {
        NSError * error = nil;
        NSData * decryptedMessage = [self.secureStorageEnryptor unwrapData:encryptedData
                                                 context:nil
                                                   error:&error];

        if (error) {
            NSLog(@"Error on decrypting message %@", error);
        } else {
            NSString * resultString = [[NSString alloc] initWithData:decryptedMessage
                                                            encoding:NSUTF8StringEncoding];
            NSLog(@"Message decrypted:\n-- %@", resultString);
        }
    }];
    NSLog(@"End of history\n\n");

Enjoy encrypted chatting and feel free to contribute!

 

Copyright © 2014-2017 Cossack Labs Limited
Cossack Labs is a privately-held British company with a team of data security experts based in Kyiv, Ukraine.