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.