End-to-End Encryption Specifications

The following contains an overview of the implemented mechanisms of the Rocket.Chat End to End Encryption feature. It provides technical information and supplements the user guide.

Encryption Process Overview

IMPORTANT: E2E encryption functionality is currently in beta and includes notable restrictions that workspace owners should carefully consider before activating this feature for production use as follow:

  • E2E encrypted messages will not appear in search operations

  • Bots or other ancillary systems interacting via webhooks or REST API will not be able to read E2EE encrypted messages

  • File uploads are not encrypted

  • Rocket.Chat enables users to reset their private E2EE key to avoid permanent loss of data during the beta period

Upon login, the client auto-generates the encryption password and asks the user to save it. This password is used to generate a secure 256-bit AES-CBC encryption key, called “Master Key.

For using end-to-end encryption (E2EE), the client (C) of a user (U) needs to have a Public-Private key pair (Ku, Kr). This key pair is generated when the user logs in with a client for the first time. The public key is sent to the server and stored in the database in the User model. The private key is first encrypted using the Master key and then sent to the server for storing in the User model database. If a public-private key pair already exists in the database for the user, instead of generating it again, it is downloaded from the server. The downloaded public key is used as-is, and the encrypted private key is first decrypted using the master key. If the master key has not been decrypted client-side already, the user is prompted to enter the master key again.

The public key is used to encrypt a persistent session key (Ks), which is then used for the actual encryption of messages and files. This encrypted session key is stored in the database, in the Subscription model for every user in a room (including the user who initiates the E2EE session). Note that this method works for direct messages as well as groups since direct messaging is just a room with only two people in it.

When starting a new E2EE session, first, if an existing session key exists in the room subscription of the current user, it is downloaded and decrypted using the user’s private key and then used to encrypt future messages. In case an existing session key is not found in the database, a new session key is generated by the current user and then stored in the database encrypted for every user in the room.

Once a session key has been obtained in the above manner, we enter E2EE mode, and all messages sent henceforth are encrypted using this session key.

Because keys are stored in the database and are persistent, the other users in the room do not need to be online to participate in an E2EE conversation.

Code

The relevant code for E2EE is located in

(rocket.chat/app/e2e/client) https://github.com/RocketChat/Rocket.Chat/tree/2bf8edab056dbc5e0d40aeae2c4472f729ec09d9/app/e2e/client__

(rocket.chat/app/e2e/client) https://github.com/RocketChat/Rocket.Chat/tree/2bf8edab056dbc5e0d40aeae2c4472f729ec09d9/app/e2e/client And https://github.com/RocketChat/Rocket.Chat/blob/2bf8edab056dbc5e0d40aeae2c4472f729ec09d9/app/e2e/client/helper.js https://github.com/RocketChat/Rocket.Chat/blob/2bf8edab056dbc5e0d40aeae2c4472f729ec09d9/app/e2e/client/helper.js

contains the technical specifications of the implementation of E2EE.

Algorithms Used

Specifically, E2EE uses:

- client key pair: RSA-OAEP, length 2048

- master key: AES-CBC, length 256, iterations: 1000

- session key: AES-CBC, length 128

Architectural Specifications

  1. User Login: As soon as the user logs in, we ask for their “E2E password”. Using this password with a PBKDF (Password-based Key Derivation Function), we generate a “Master Key.” We then check the server database for whether a public-private key pair exists for this user. If it does, we download that key pair. The public key is used as-is. The private key is in encrypted form and will be decrypted using the master key before it can be used.

  2. Client Startup: Using startClient() in rocketchat.e2e.js, check the local storage of the client to determine whether this is a new client or not. If local storage does not have the public-private key pair for this client, then this is treated as a new client, and this RSA-OAEP key pair is generated using a function call to: crypto.subtle.generateKey({name: 'RSA-OAEP', modulusLength: 2048, publicExponent: new Uint8Array([0x01, 0x00, 0x01]), hash: {name: 'SHA-256'}}, true, ['encrypt', 'decrypt']); This key pair is finally stored in the client’s local storage in a serialized form (JSON-stringified). This serial form of the public key and encrypted private key is also sent over to the server database using a server-method call to addKeyToChain().

  3. The client starts the encryption session from the E2E flex tab button: The handshake() function in rocketchat.e2e.room.js is called for the room where the client started the session. First, try fetching an encrypted session key from the subscription for this user-room pair from the database. If this key is found, decrypt this using the client’s private key, and the E2EE session is started using this decrypted key. If the user’s subscription to this room does not contain a key, generate a new AES-CBC session key for use. This is done by a function call to: RocketChat.E2E.crypto.generateKey({name: 'AES-CBC', length: 128}, true, ['encrypt', 'decrypt']) Once a key has been generated, it has to be stored, encrypted in the subscriptions of all users in the current room. This is done by: Fetch public keys for all users in the room using server-method calls to getUsersOfRoom() and fetchKeychain(). One by one, encrypt the newly generated session key using each of these public keys, and store this encrypted key in the corresponding user’s subscription to this room using a server-method call to updateGroupE2EKey(). E2EE session is now started using the generated session key. 4. Client sends a message: Making use of the onClientBeforeSendMessage event when the user sends the message, the message object is encrypted using the session key obtained in the previous step when E2E is in session. This encrypted message is wrapped in another object, in a “msg” parameter, and a new parameter for type, called “t”, is added to this new object with the value “e2e” to distinguish it from a regular message. Thus, the new object becomes:

final_message: {

msg: <encrypted_message>,

T: “e2e” } This new object is sent to the other client. Note that the original message is not visible to anyone, and only the encrypted message is sent.

  1. The client receives a message: When a message object is received, using the `onClientMessageReceived event, we intercept it and check whether the “t” type parameter of the message object is “e2e”, like we set when sending the message. If it is not, we don't need to decrypt it as it is a plaintext message, and E2EE was not used. If it is an encrypted message, we take the “msg” parameter’s value and decrypt that message using the session key.

    Note that if the receiving client does not have the session key in its local storage, it will have to download the encrypted session key from the server using the fetchGroupE2EKey`method, and then decrypt it using its own private key, and then use this key for decryption of the incoming message.

Server Methods

1. addKeyToChain(key)

For saving a newly generated public key to the database for the current user. Request:

key = {

RSA-PubKey: “< The generated public key for a client>”,

RSA-EPrivKey: “< ``Generated private key, encrypted using the master key>”

}

The keys have been converted from ArrayBuffer to String format before sending them.

Response: null

2. fetchGroupE2EKey(rid)

For fetching the encrypted session key for a conversation, for the current user.

Request:

rid = The room id of a conversation (either of direct or private).

Response:

The E2EE session key for the current user for that conversation.

3. fetchKeychain(userId)

For fetching the public key of a user (to be used for encrypting the session key for that user).

Request:

userId = The id of a user

Response:

{

RSA-PubKey: “< The public key for that user>”,

RSA-EPrivKey: “< The private key, encrypted using the master key>”

}

4. updateGroupE2EKey(rid, uid, key)

For saving an encrypted session key to the database for a user.

Request:

rid = The room id of a conversation (either of direct or private)

uid = The id of a user

key = The E2EE Session key for that user, for that conversation

Response:

{ Rocket.Chat subscription object }

5. emptyKeychain()

Clears out the current user’s public and encrypted private keys from the server database.

Request: {}

Response: {}

6. fetchMyKeys()

Fetches current user’s public and encrypted private keys from the server database.

Request: {}

Response:

{

RSA-PubKey: “<The public key for current user>”,

RSA-EPrivKey: “<The private key, encrypted using the master key>”

}

Each route directly corresponds to one server DDP method described above. These routes follow the same naming system as the DDP methods described above. Please refer to the above description to know more about the individual request/response pairs for each route.

GET e2e.fetchGroupE2EKey(rid)

GET e2e.fetchKeychain(uid)

GET e2e.fetchMyKeys()

POST e2e.addKeyToChain(RSAPubKey, RSAEPrivKey)

POST e2e.emptyKeychain()

POST e2e.updateGroupE2EKey(uid, rid, key)

Push Notifications of End-to-End encrypted messages

Push Notifications for messages of an E2EE room just contain the encrypted payload of a message, the job of decrypting this content before shown is done locally by the mobile clients (iOS/Android).

Process

The server sends Push notifications. The server, however, doesn't store the unencrypted content of any message from an E2EE room, because only the encrypted string is stored on the server. These encrypted strings can only be decrypted using the private key of a user that is stored locally on clients.

When a new push notification from a E2EE message arrives, it has a messageType: 'e2e'. The mobile client then starts decrypting the message, within the push payload, checking for the locally stored private key of a user and the E2EE key of the room that the message came from. If both are found, the message is decrypted locally on the device and then shows the plaintext message. In this process, only the encrypted message content passed via the push notification gateways.

This feature is available in our Community Edition.

Fetching full message content from the server on receipt (Enterprise Edition only)

To add an additional layer of security, there exists another feature for push notifications:

Fetch full message content from the server on receipt

This means to request the push message content from the server to display it, and it does not pass any message content - encrypted or not - via Google/Apple/other push gateways. Instead, the message content itself is fetched by and within the Rocket.Chat client itself. What passes via the gateways is only the information that a new message should be fetched and then shown as a push notification. Once the client receives this, the client will fetch the content. This way, you can prevent that the message content (even in encrypted form) passes via a separate gateway.

Note: This feature is Enterprise Edition only.

Off-the-record Messaging (OTR) encryption specifications

OTR is closely related to End-to-End-Encryption. It uses the same ciphers but instead uses only the participant´s local session storage to store the keys.

Keys are exchanged when the OTR invitation is accepted by the counterpart, that is why all participants need to be online. Messages from an OTR session are removed when the session storage is cleared.

Last updated

Rocket.Chat versions receive support for six months after release.