Rocket.Chat provides end-to-end encryption (E2EE) for secure communication. In an E2EE session, only the participants can decrypt and view messages, ensuring that no third party (including the server) can access the content.
This document explains the technical implementation of E2EE in Rocket.Chat, including key generation and encryption processes, and supplements the user guide.
Key concepts and components
IMPORTANT: E2E encryption functionality includes notable restrictions that workspace owners should carefully consider before activating this feature. Here are what to keep in mind:
Encrypted messages of encrypted rooms will not be found by search operations.
Bots may not be able to see encrypted messages until they implement support for it.
Spotted a bug? Help us improve by reporting it directly to Rocket.Chat!
Encryption password and Master Key
When a user logs in, the client auto-generates an encryption password and prompts the user to save it. This password generates a secure 256-bit AES-CBC encryption key called the “Master Key.” The Master Key is central to encrypting and decrypting the user’s private key.
Public-private key pair (Ku, Kr)
Each user has a public-private key pair (Ku, Kr) generated by the client upon first login. The public key (Ku) is stored on the server, while the private key (Kr) is first encrypted using the Master key and then also sent to the server for storage in the User model database.
If a public-private key pair already exists in the database for the user, instead of being generated 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 on the client side already, the user is prompted to enter the master key again.
Session key (Ks)
The public key encrypts a persistent session key (Ks), which is then used to encrypt 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).
This method works for direct messages and groups since direct messaging is just a room with only two people in it.
Message encryption and decryption
When starting a new E2EE session, if an existing session key exists in the current user's room subscription, it is downloaded and decrypted using the user’s private key and then used to encrypt future messages. If an existing session key is not found in the database, the current user generates a new one, which is then encrypted for every user in the room and stored in the database.
Once a session key has been obtained above, 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 references
The relevant code for Rocket.Chat’s E2EE implementation is located in the following locations:
Algorithms and encryption standards
Client key pair: RSA-OAEP, 2048-bit
Master key: AES-CBC, 256-bit, with 1000 iterations (PBKDF)
Session key: AES-CBC, 128-bit
Encryption process
User login and key generation: Users are prompted for their E2EE password when they log in. This password generates the Master Key using a Password-Based Key Derivation Function (PBKDF). The server checks if the user already has a public-private key pair stored. If found, the private key is downloaded and decrypted using the Master Key. If not, a new key pair is generated.
Client startup: Using
startClient()
inrocketchat.e2e.js
, the client’s local storage is checked to determine whether this is a new client or not. If the local storage does not have the public-private key pair for this client, then this is treated as a new client, and a new 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()
.Starting an E2EE session: The
handshake()
function in rocketchat.e2e.room.js is executed when a client starts a session in a room. The process begins by attempting to retrieve an encrypted session key from the database for the given user-room pair. If this key is found, it is decrypted using the client’s private key, and the E2EE session is initiated with the decrypted key.A new AES-CBC session key is generated if no session key is found for the user’s subscription to the room. This is accomplished by calling the function:
RocketChat.E2E.crypto.generateKey({name: 'AES-CBC', length: 128}, true, ['encrypt', 'decrypt'])
Once the new key is generated, it must be encrypted and stored in all users' subscriptions in the current room. To do this:
Fetch the public keys for all users in the room using server-method calls to
getUsersOfRoomWithoutKeys()
, which returns theuserId
plus the public key from this user. This is done in batches, so some users are expected to receive keys before others.Encrypt the newly generated session key with each user’s public key.
Call
provideUsersSuggestedGroupKeys()
. This will add the newly encrypted Session Key as asuggestedKey
, on the user’s subscription. This process doesn’t require all users to be online simultaneously. As long as a user with the Session Key is online, the exchange of keys will continue to work until all users in the room (with valid public keys) have a suggested key.Upon login, users will receive the suggested keys that have been shared with them via their subscription. Users will then evaluate the keys, attempt to import them into the E2E worker, and either accept or reject them.
If a user accepts the key, the Suggested Key is moved into the E2EKey property on their subscription, meaning that the user now has a valid Session Key and can share this key with others.
If a user rejects the key, the Suggested Key is removed from the subscription, and the user is put on the virtual queue of users waiting for keys, allowing other users to provide them with keys.
With these steps completed, the E2EE session is successfully started using the newly generated session key.
Client sends a message: The
onClientBeforeSendMessage
event is triggered when a user sends a message. The message object is encrypted using the session key obtained in the previous step when E2EE is in session. The encrypted message is then placed inside a new object. This object contains two parameters:msg
: the encrypted message.t
: a type parameter set to “e2e” to identify it as an encrypted message rather than a regular one.
The structure of the new message object now looks like this:
final_message: { msg: <encrypted_message>, t: "e2e" }
This new object is then sent to the recipient. As a result, the original message remains invisible to anyone except the intended recipient, and only the encrypted message is transmitted.
The client receives a message: When a message object is received, using the
onClientMessageReceived
event, we intercept the message and check whether thet
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 plain text message, and E2EE was not used. If it is an encrypted message, we take the “msg” parameter’s value and decrypt it 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. Then, use this key to decrypt the incoming message.
APIs
1. e2e.setUserPublicAndPrivateKeys
Saves a user’s public key and encrypted private key to the server.
Request:
key = {
RSA-PubKey: <public_key>,
RSA-EPrivKey: <encrypted_private_key>
}
Response: { success: true }
2. fetchMyKeys()
Fetches current user’s public and encrypted private keys from the server database.
Request: {}
Response:
key = {
RSA-PubKey: <public_key>,
RSA-EPrivKey: <encrypted_private_key>
}
3. e2e.getUsersOfRoomWithoutKey
Fetches a list of public keys plus userIds
of users on the selected room that are waiting for E2E keys.
Request:
rid = the room's ID
Response: { success: true, users: [{ userId: ‘xxx’, public_key: ‘xxxx’ }] }
4. e2e.setRoomKeyId
Defines the keyID
for the room. KeyID is derived from the encryption key and is used to identify the right encryption key in rooms with multiple room keys.
Request:
rid = the room id
keyID = the keyID for the room
Response: { success: true }
5. e2e.updateGroupKey
Updates a user’s Group (Session) Key. When a user creates a new conversation, after generating the Session Keys, the user defines this Session key on their own Subscription. After this, the user may start sharing the key with other users.
When the uid
parameter is different from the user who’s calling the endpoint, the sent encrypted key will be defined as uid
’s Suggested Key.
Request:
uid = the user we want to define the key for
rid = the room id
key = the encrypted Session Key
Response: { success: true }
6. e2e.acceptSuggestedGroupKey
Upon validating a suggested key, mark the key as valid and define it as the proper Session Key for the user. If there’s a suggested oldKeys
, they’re also accepted.
Request: {}
Response: { success: true }
7. e2e.rejectSuggestedGroupKey
Upon validating a suggested key, remove it from the user’s subscription and put the user back into the waiting queue.
Request: {}
Response: { success: true }
8. e2e.provideUsersWithSuggestedGroupKeys
Provides a list of users waiting for keys on the room with a suggested key for the room. This key will be stored in the E2ESuggestedKey
property on each user’s subscription so they can validate the next time they’re online.
Request:
usersSuggestedGroupKeys = {
userId = the user id
key = the suggested, encrypted, session key
oldKeys = array of old keys used for encryption on room
}
Response: { success: true }
9. e2e.resetRoomKey
Allows an authorized user to reset the Session Key of a room for all users.
rid = the room id
e2eKey = the new session key
e2eKeyId = the new session key ID
Response: { success: true }
Push notifications of end-to-end encrypted messages
Push Notifications for messages in an E2EE room just contain the encrypted payload of a message. The job of decrypting this content before it is shown is done locally by the mobile clients (iOS/Android).
Process
The server sends push notifications. However, it 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 a user's private key, which is stored locally on clients.
When a new push notification from an E2EE message arrives, it has a “messageType: 'e2e
'“. The mobile client then starts decrypting the message within the push payload, checking for a user's locally stored private key and the E2EE key of the room where the message came from. If both are found, the message is decrypted locally on the device and then shows the plaintext message. Only the encrypted message content is passed via the push notification gateways in this process.
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 the message content (even in encrypted form) passes via a separate gateway.
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 counterpart accepts the OTR invitation, so all participants need to be online. Messages from an OTR session are removed when the session storage is cleared.
Multiple room keys + reset room keys
Given the nature of E2EE, when all users lose their Session Keys for a conversation, the contents of previously encrypted messages cannot be recovered without an attempt to break the encryption, which may not be possible.
On Rocket.Chat, the same holds true. If all participants in a conversation lose access to their Session keys, the conversation is gone forever. However, this presents a problem: even when the messages cannot be decrypted anymore, you may still need to continue the conversation in the same room instance without creating a new room or disabling the E2EE status of the room.
To prevent this from happening, the possibility of resetting the encryption keys for a conversation has been added. After a user resets the Session keys for a room, all users’ previous keys will be removed from their subscription, allowing them to receive a new key.
The process goes as follows:

E2EE room key reset flow
User A resets the Session Key in room Z
User A creates a new Session Key and derives a new KeyID
The server then removes the
E2EKey
property from all users in the room.A new room key is distributed to the other users, and the conversation may continue.
The terms session key and room key are used interchangeably in this document.
However, what happens if some users still have an E2EKey
on their subscription? The server doesn’t know if this key continues to be valid, so to prevent unwanted data loss, it will move the user’s E2EKey to a new property called oldRoomKeys
. This array contains keys that were on the user subscription when a room key reset happened. Clients will then use this information to try to decrypt old messages.
The decryption process is as follows:
Message is:
keyID
+ encrypted contentThe client will attempt to match the
keyID
prepended on message with thekeyID
stored on the room object (current session key)If keyIDs match, the Client will use the current Session Key to decrypt the message.
If this fails, a message will be presented on the UI with the legend “Message cannot be decrypted due to a key error”
If keyIDs don’t match, the Client will try to find a key with the given
keyID
on the list ofoldRoomKeys
If a key is found, the message will be decrypted using that key
If a key is not found, the Client will try to decrypt using the latest key
If any of these attempts fail, the message is deemed to be indecipherable.
The same decryption process is followed by the file encryption.
If a user has old room keys, the key suggestion process has been accommodated to allow these users to share their old keys in the same fashion as the current encryption key.
The list of oldRoomKeys
is limited to 10 elements. After this limit is reached for a user, the oldest key will be removed, rendering old messages indecipherable. When this happens, the Client will present the legend “This message cannot be decrypted due to multiple room key resets.”
FAQs
If there are still users with an E2EKey
for the room, why not wait for this user to return online to share the key?
E2EKey
for the room, why not wait for this user to return online to share the key?Rocket.Chat has no way to identify if these keys are valid or not. A user may have a Session Key but may have forgotten his Master Key, rendering the Session Key useless. To accommodate all these cases, we allow authorized users to reset the session key when lost. This way, users decide when it is required to do it.
If I have to lose my keys to see the reset room key button, doesn’t that mean I won't ever have old room keys?
For now, yes. However, in the future, we plan to allow all users to share the old keys with others, allowing them to have full access to the conversation until all users have the same set of old room keys. This is not implemented right now.