Skip to main content

Developing a real-time secure chat application like WhatsApp & Signal with end-to-end encryption

Lately, there is a lot of fuss around end-to-end encrypted chat applications. WhatsApp and Signal are two messaging apps dominating the headlines, let's take a look at why - WhatsApp recently updated its privacy policy, stating that the messaging platform will share user data with other Facebook-owned and third-party apps. This has prompted several users to look for alternative platforms, top among them is Signal. Signal is essentially an encrypted messaging app. Messages sent through Signal are said to be encrypted, meaning the platform cannot access private messages or media, or store them on their server. This is called end-to-end encryption. End-to-End Encryption(E2EE) is the most important feature in real-time chat applications. Our article will cover:

But first, let’s look at what Real-time Systems mean?

A real-time system means sending and receiving of data instantly over a network among multiple clients. One may specify this as bi-directional flow. This enables users to make the right decisions at the right time. In simple systems, data transfer usually takes place through a request-response mechanism using a client-server architecture.

Let’s find out what can be a suitable mechanism for our application.

Use Case - Healthcare

End-to-end encrypted chat application

Our application is based on conversations in a Healthcare system. These conversations take place between doctors and their respective patients. This means that the patients will only be able to see their doctor in the contact list and vice versa. Therefore the users are categorized on the server based on their ‘role’. Also, E2EE is needed here to keep a patient’s details/conversations secure and confidential.

Technology Stack

Client Application

Server Application

The Challenge - Making it real-time

For any app to feel real-time, the user needs to be kept updated with any activity happening as soon as possible. The challenge arises in selecting and implementing a suitable development technique. With the traditional request-response model, we have few options:

The user might refresh the web page time-to-time to check for message updates. But that is not an optimal solution. This may result in bad UX.

The concept of HTTP request-response is widely used. But this requires establishing a TCP connection every time data is sent to the server. Being a one-way synchronous communication protocol, this may result in a lot of overheads while creating and destroying a TCP connection every time a message is sent in real-time chat applications.

WhatsApp & Signal with end-to-end encryption

This version of HTTP eliminates the need for opening a TCP connection for each HTTP request. This means that it helps in maintaining a persistent connection. But it still does not provide us with full-duplex communication as required in real-time applications.

An AJAX based-timer! Means that the client sends HTTP requests time-to-time and the response is immediately given by the server. Although it is asynchronous, it uses a lot of resources, thus creating traffic. The resources are immediately released but cannot be used in heavy applications as in real-time.

This involves less traffic as compared to short polling. Here, the responses are not immediate, but this makes the application hold the resources for some time, hence leaving the requests unresolved. Also, we have to perform re-authentication or re-authorization several times. Again, this is not a good option for real-time applications.

This is a mechanism used by the server to update the client whenever any event takes place. It is quite useful in real-time applications and it performs like a one-way publish-subscribe model. We want our application to perform bi-directional communication.

WhatsApp & Signal with end-to-end encryption

Source: Polling vs SSE

Web Sockets

This concept resolves most of the issues we just discussed. It implements instant two-way communication of messages with a persistent connection just as required for developing a real-time system. NodeJS offers several libraries to implement this technology. What we will be utilizing for our application is the Web Socket API with ‘WebSocket’ library.

According to MDN, “The WebSocket API is an advanced technology that makes it possible to open a two-way interactive communication session between the user's browser and a server. With this API, you can send messages to a server and receive event-driven responses without having to poll the server for a reply”.

WhatsApp & Signal with end-to-end encryption

Step 2: Client Initiates request with “ws” protocol in URL

WhatsApp & Signal with end-to-end encryption

Properties of the WebSocket (ws) Object

End-to-end encrypted chat application

Step 3: Server listens for TCP socket connection using ‘WebSocket’ library of NodeJS

WhatsApp & Signal with end-to-end encryption

Step 4: Status Code - 101 Switching Protocols; Check-in Network>>WS>>Headers

End-to-end encrypted chat application

Event Listeners of “ws” object at Client

End-to-end encrypted chat application

Sending New Message to Server

End-to-end encrypted chat application

Handling Web Sockets events at the Server

End-to-end encrypted chat application

Exchanging Messages using Web Sockets; Check-in Network>>WS>>Messages

End-to-end encrypted chat application

Fetching Chats from Local Storage using getItem()

End-to-end encrypted chat application

Storing New/Updated Chats to LocalStorage using setItem() on websocket event ws.onMessage()

End-to-end encrypted chat application

Storing Messages in LocalStorage of Web Browser for Each Client

The closing handshake can take place either by the client or the server. Reconnection has to be done manually.

End-to-end encrypted chat application

Real-Time Chat Application Architecture: High-Level Diagram using Web Sockets

End-to-end Encryption

Now that we have our messages transferring instantly from client to server and back, let’s discuss how we can make our data secure over the network. Various algorithms and protocols are working on the internet these days to make the exchange of confidential information secure. Messaging applications do implement encryption, but not each one of them makes the encryption end-to-end. This means that not even the server can decrypt our messages. But why do we need to make the application that secure?

End-to-end encrypted chat application

Source: wikimedia.org

The answer is simple - to make the user’s private information hidden from any third party user. This may be the government, hackers or any other intelligence agency. The service provider may or may not allow third-parties like the government to access the data as in the case of any criminals or terrorist activities. But what if the servers get hacked? The information might then be in the wrong hands. In such cases, the users prefer to choose end-to-end encryption, where even the service provider cannot access decrypted data.

While many applications mention that they implement end-to-end encryption, only a few of them prove to do so. The very famous Telegram application provides an optional feature of Secret chats, using a protocol named “MTProto”.

While Whatsapp, Facebook and Signal Messenger use the Signal Protocol developed by the Open Whisper Systems, only the Signal Messenger proved to be the most secure application.

This is because it encrypts the metadata as well, and has also denied the intelligence agencies to provide them with any user’s information. Moreover, the protocol is available as open-source code to be used or cross-verified by other developers, which makes it the most trustworthy messaging application.

End-to-end encrypted chat application

Today we will be discussing the Signal Protocol in detail. But before that, we need to be aware of the Diffie-Hellman key exchange mechanism. With simple encryption, the messages are usually encrypted only between the users and the server, making use of some cryptographic keys, hence making data vulnerable at the server. We want these keys only to exist between the users and not the server. But how is this possible? Suppose we have two Clients - Alice and Bob.

WhatsApp & Signal with end-to-end encryption

Image Source: Diffie Hellman key exchange

This mechanism was developed by Whitfield Diffie andMartin Hellman to derive the cryptographic keys instead of exchanging them completely in public. It is explained using colors since it is not possible to separate colours once mixed. Similarly, it is hard to figure out the secret keys using the only public components, once combined mathematically with the prime numbers provided by the server.

Although the mechanism provides us with a secure way to create cryptographic keys as end-to-end, it does not authorise the users. Hence, we might have some third party pretending to be the intended recipient and he/she will be able to access or modify the messages, by creating another pair of shared secret keys with Alice and Bob respectively. This is usually known as a Man-in-the-middle attack.

To perform authentication, this algorithm is integrated with other algorithms that provide authentication (ECDH) or derived multiple times mathematically (X3DH). That is when RSA came to rescue. The sender not only performs Diffie-Hellman but also shares his/her signature to ensure that only he/she has sent that message.

End-to-end encrypted chat application

Source: audible.in

Here is an example of the audible website, where you may see that the security protocols being used are TLS 1.2, ECDHE_RSA, AES, and not DH alone. This is how TLS, VPNs, and HTTP work. However, this algorithm was very slow and didn’t provide perfect forward secrecy. Wait, what is Forward Secrecy now?

So we are discussing here X3DH key agreement protocol in detail as it is being used in the Signal Protocol. This is useful in asynchronous communication as well as authentication. For example, Bob has published some information for Alice, but she is currently offline, then the server might temporarily hold the data or send a notification to Alice.

This algorithm makes use of public components of identity keys (IK), ephemeral key (EK), signed prekey (SPK), and one-time prekey (OPK). The private components are stored at the respective user devices for computation and not shared.

End-to-end encrypted chat application

The algorithm performs Diffie-Hellman four times, ensuring mutual authentication (DH1 & DH2)  and Forward Secrecy (DH3 & DH4), using a Key Derivation Function or KDF which is quite similar to a hash function.

This produces one master secret key, SK = KDF(DH1 || DH2 || DH3 || DH4) at the client’s respective devices, that can now be used by Alice and Bob to encrypt and decrypt the messages. To prevent man-in-the-middle attack, the Identity public keys are mathematically combined into a Safety Number using a hash function, which only the sender and receiver will have at their respective ends. This can be in the form of a QR code or fingerprint scan.

Still, wondering what Forward Secrecy is? This ensures that future messages shall not be accessed by any third party even when he/she gets access to the public keys. We will discuss this in more detail in the next algorithm.

WHY DOUBLE RATCHET? We got end-to-end encryption using X3DH, we also achieved forward secrecy and mutual authentication in asynchronous communication. Now, why does the Signal protocol still need another algorithm? When a user is offline, it gives an attacker a lot of time to find and use public keys available at the server. Since the key is always the same for a long period, it makes the messages vulnerable. You need to update the keys regularly! In messaging applications like Signal and Whatsapp, these keys are updated for every message. For implementing this, the Double Ratchet algorithm came into play.

A ratchet function is a function that can turn one way only, i.e. it cannot move backward. What we will be using here is called a KDF Ratchet, since you cannot go back to figure out what the key was. This function works as follows-

End-to-end encrypted chat application

Source: Signal - Double Ratchet

If the attacker gets one key, he/she will not be able to undo the operation performed by KDF Ratchet to figure out the input data, but he/she will only be able to access future messages. That’s a huge problem.

To ensure future secrecy, we use a Diffie-Hellman Ratchet with the KDF Ratchet function of Alice and Bob, forming a Double Ratchet. In such a session, we have three chains on both ends, i.e. a Root chain, Sending chain, and Receiving chain.

The sending chain of Alice is synchronized with the receiving chain of Bob and vice versa. These start at the same time. In case of any asynchronous event like non-receival of messages or delay or misuse of keys, the receiver keeps a check on the key which is not deleted until all messages are received.

Steps for Double Ratchet mechanism -

The Diffie-Hellman parameters manipulate the KDF chain to reset the sending and receiving chains of both Alice and Bob by updating their starting positions and making them synchronous again. If someone cracked a key, we can re-establish the secrecy from then on. For example, Bob can send DH public-key (dh2) to Alice’s DH ratchet that will reset the sending and receiving chains on both ends. Moreover, as soon as you decrypt a message using the key, you delete it immediately and carefully. Hence the end-points are also safe from future attacks.

Cryptographic Properties as a result of the Double Ratchet Mechanism -

End-to-end encrypted chat application

Step 1: Add File libsignal-protocol.js to Client

Note: The libsignal-protocol.js is open source, taken from the link mentioned above. It includes all the algorithms which we discussed till now i.e. X3DH and Double Ratchet. These are implemented in the Signal Protocol for the Signal Messenger application for mobile and desktop. And, we will implement this in our Web Browser using LocalStorage.

Initialisation

End-to-end encrypted chat application

Step 2: Create a file SignalGateway.js and initialize a manager for each User

End-to-end encrypted chat application

Generate Identity Key & Registration ID for each User

End-to-end encrypted chat application

Generate Prekey Bundle on Login (actually done on Application installation)

End-to-end encrypted chat application

Storing the New prekey bundle to LocalStorage using setItem()

Note:The utilities are taken from helpers.js of Signal Protocol. Need to convert data format because the keys are stored and processed in the form of Array Buffer in the Signal Protocol.

End-to-end encrypted chat application

Initialization of Signal Server Store and Signal Protocol Manager takes place from App.js with the LoggedIn User ID, Name and respective Prekey bundle as parameters

End-to-end encrypted chat application

Stored Prekey bundle to LocalStorage of Web Browser for each user (actually stored temporarily at Signal Server using a secure TLS connection)

Encryption

End-to-end encrypted chat application

Calling encryptMessageAsync() of SignalGateway.js from chatWindow.js before sending data to Server

End-to-end encrypted chat application

Step 3: Encrypting Messages in SignalGateway.js using the methods of libsignal-protocol.js and InMemoryProtocolStore.js

End-to-end encrypted chat application

Fetching Prekey bundle from LocalStorage of Browser using getItem() for Encryption and Decryption

Decryption

End-to-end encrypted chat application

Step 4: Decrypting Messages in SignalGateway.js using the built-in methods of libsignal-protocol.js and InMemoryProtocolStore.js

End-to-end encrypted chat application

Calling decryptMessageAsync() of SignalGateway.js from chatWindow.js on “onMessage” event of ws

End-to-end encrypted chat application

Asymmetric Encryption Architecture - High-Level Diagram with Integration of the Signal Protocol

End-to-end encrypted chat application

Calling Login API

End-to-end encrypted chat application

API to fetch LoggedIn user

End-to-end encrypted chat application

User Controller method to fetch LoggedIn user on API call

End-to-end encrypted chat application

Calling getContacts API

End-to-end encrypted chat application

API to fetch all Contact except the logged-in user with the given role

End-to-end encrypted chat application

User Controller method to fetch All contacts from Database

End-to-end encrypted chat application

Architecture: Low-Level Diagram of Client (Web Browser)

Limitations in the Current Approach

End-to-end encrypted chat application

Proposed Architecture: High-Level Diagram - Client & Server

End-to-end encrypted chat application

Example: Signal Messenger Architecture

Watch my session on Developing a real-time secure chat application with NodeJS -

Conclusion & Future Work

We discussed the importance of Web Sockets technology and end-to-end Encryption, and how these are implemented to develop a real-time secure chat application. The signal protocol being the more secure and trustworthy protocol provides its code as open source. We also used REST APIs for login operations and to fetch contacts based on role. We implemented the WebSocket library, one of the many libraries available for implementing Web Sockets API in NodeJS.

However, much more features can be added to our simple chat application. Such as group messages, online-offline features, guaranteed message delivery, temporary message storage at a separate server, load balancing, and much more that we discussed with the proposed architecture. Firebase can also be used for building a real-time chat application, which internally uses the concept of Web sockets.

JS

Resources

We'd love to talk about your business objectives

Written by