undefined

End-to-end encryption via OTR

Overview

End-to-end encryption (E2EE) is a system of communication where only the communicating users can read the messages.

The following libraries can be used to integrate end-to-end encryption capabilities into applications:

It is possible to integrate end-to-end encryption on top of ConnectyCube messaging capabilities. Actually any of the above encryption protocols can be used, however we will review here the OTR protocol because there are lots of client-side libraries ready to use and because of its license.

OTR

Off-the-Record (OTR) Messaging allows you to have private conversations over instant messaging by providing:

  • Encryption No one else can read your instant messages.
  • Authentication You are assured the correspondent is who you think it is.
  • Deniability The messages you send do not have digital signatures that are checkable by a third party. Anyone can forge messages after a conversation to make them look like they came from you. However, during a conversation, your correspondent is assured the messages he sees are authentic and unmodified.
  • Perfect forward secrecy If you lose control of your private keys, no previous conversation is compromised.

Here we are going to explain how to implement End-to-end encryption (E2EE) in you app using OTR Messaging Protocol implemented in JavaScript

Connect OTR JS lib

Include the build files on the page:

<!-- Load dependencies -->
<script src="build/dep/bigint.js"></script>
<script src="build/dep/crypto.js"></script>
<script src="build/dep/eventemitter.js"></script>

<!-- Load otr.js or otr.min.js -->
<script src="build/otr.min.js"></script>

You can get these files at OTR for JS lib git repository

Prerequisites

Before initiating the encryption process between 2 users you have to have an active chat connection. Please follow the messaging API guide to get more info how to do it.

Initial setup

For each user you're communicating with, instantiate an OTR object:

const otrConfig = {
    // long-lived private key
    priv: myKey,

    // turn on some debuggin logs
    debug: true,

    // fragment the message in case of char limits
    fragment_size: 140,

    // ms delay between sending fragmented msgs, avoid rate limits
    send_interval: 200
};

const otrClient = new OTR(otrConfig);

We recommend to compute your long-lived key beforehand. Currently this is expensive and can take several seconds:

// precompute your DSA key
const myKey = new DSA();

Setup callbacks

otrClient.on('ui', (msg, encrypted, meta) => {
    // Use this callback to display a decrypted message to a user
});

otrClient.on('io', (msg, meta) => {
    // Send here an encrypted message to other user
});

otrClient.on('error', (err, severity) => {
    // ...
});

otrClient.on('status', state => {
    // Status of encryption initialization process
    // Possible states:
    // OTR.CONST.STATUS_SEND_QUERY  : 0
    // OTR.CONST.STATUS_AKE_INIT    : 1
    // OTR.CONST.STATUS_AKE_SUCCESS : 2
    // OTR.CONST.STATUS_END_OTR     : 3
});

Authenticated key exchange

Initially, messages are sent in plaintext. To manually initiate the process of encryption you need to initiate the authenticated key exchange:

otrClient.sendQueryMsg();

After this the io callback will be fired and there you need to send this request to other user, as below:

otrClient.on('io', (msg, meta) => {
    const message = { type: 'chat', body: msg };

    ConnectyCube.chat.send(otherUserId, message);
});

At other user's side you are receiving this message and call receiveMsg on the OTR client which then confirms the authenticated key exchange process (internally):

ConnectyCube.chat.onMessageListener = function(userId, message) {
    otrClient.receiveMsg(message.body);
};

To track the authenticated key exchange process both users can use the status callback:

otrClient.on('status', state => {
    if (state == OTR.CONST.STATUS_AKE_SUCCESS) {
        console.log('authenticated key exchange done');
    }
});

When everything is done - you will receive the OTR.CONST.STATUS_AKE_SUCCESS status. So now you have an active End-to-end encryption channel and can securely exchange messages between parties.

Exchange encrypted messages

Now you have a secure p2p channel, so you can exchange messages privately.

To send an encrypted messages use the sendMsg method:

otrClient.sendMsg('How are you maaaan?');

After this the io callback will be fired and there you need to send this request to other user, as below:

otrClient.on('io', (msg, meta) => {
    const message = { type: 'chat', body: msg };

    ConnectyCube.chat.send(otherUserId, message);
});

At other user's side you are receiving this message and call receiveMsg on the OTR client which then will call the ui callback:

ConnectyCube.chat.onMessageListener = function(userId, message) {
    otrClient.receiveMsg(message.body);
};

otrClient.on('ui', (msg, encrypted, meta) => {
    // Use this callback to display a decrypted message to a user
});

Close secure connection

To end an encrypted communication session use the following code snippet:

otrClient.endOtr(() => {});

After this the status callback will notify a OTR.CONST.STATUS_END_OTR status and the OTR engine will return the message state to plaintext.

Code sample

We have a simple code sample available on GitHub on how to integrate OTR into Web JS app: https://github.com/ConnectyCube/connectycube-js-samples/tree/master/end-to-end-encryption

Have any issues?

Send us a support request via Contact form and we will answer quickly.