Freedom Chat's End-to-End Encryption (E2EE)
In this article we provide a detailed analysis of Freedom Chat’s end-to-end encryption.
End-to-End Encryption in Freedom Chat
End-to-end encryption (E2EE) is crucial for modern messaging apps and is designed to prevent anyone other than the intended recipient of a message from reading it. When E2EE is not implemented (either knowingly or improperly), not only can the provider of the messaging app you’re using be able to read your messages but malicious hackers and even government agencies can too. In fact, research estimates that over 5.5 billion unencrypted texts are sent each day!
We created Freedom Chat to give everyone control over their digital lives where you can communicate with whomever you want (privately), protect your data, and avoid having your conversations stolen from you.
End-to-End Encryption Meaning
End-to-end encryption works by leveraging asymmetric cryptography, a type of encryption first invented by GCHQ in 1973 and kept classified until 1997. The public version of this encryption was released a few years later in 1977 by Rivest, Shamir, and Adleman, who founded the company RSA. Asymmetric cryptography is central to modern communication: almost all internet or phone communications are protected using this method, including the internet connection you’re using to read this article.
The way these algorithms work involves each participant generating a “private key,” which is kept secret. From this private key, the participant derives a corresponding “public key,” which is shared publicly. Using someone’s public key, a user can encrypt a message that only the holder of the private key can decrypt. Often, a public key is compared to an open vault, and the private key to the code for that vault: anyone can close the vault, but only the person who knows the code can open it again.
In essence, when a user wants to encrypt a message for another user, they simply use the recipient’s public key for encryption. The encrypted message is then sent via Freedom Chat servers, and the recipient uses their private key to decrypt it.
In Freedom Chat, RSA keys with a modulus size of 4096 bits for asymmetric cryptography are generated upon account creation, then stored and encrypted with a symmetric key in the device’s keychain.
Trust On First-Use
An attack scenario described in the previous paragraph involves how the public key is sent from the recipient to the sender. If an attacker manages to intercept and replace the recipient’s public key with one controlled by the attacker, the sender would then use the attacker’s public key to encrypt the secret message, inadvertently sending it to the attacker instead of the intended recipient.
This attack is known as a “man-in-the-middle attack.” To prevent such an attack, there needs to be a mechanism to authenticate the recipient’s public key to ensure that a legitimate public key is used later in the process.
In Freedom Chat, we have multiple layers to ensure this authentication and prevent a man-in-the-middle attack:
Freedom Chat locally stores the
sealdIdof any user it has interacted with. If anyone tries to swap the keys with attacker-controlled public keys after they have been stored for the first time, this would be detected, and the user would be notified.Freedom Chat maintains a signature chain for each user, where various public keys are mutually signed along with the
sealdId. This setup ensures that if anyone attempts to maliciously update a user’s keyring, any newly added public keys would be detected and rejected. Here is the helper function used to detect such discrepancies:
const getSealdIds = async(chatId, phoneNumbers) => {
let discrepancies = 0;
const query = db.collection("sealdIdentities").where(FieldPath.documentId(), "in", phoneNumbers);
const snapshot = await query.get();
const localMembers = await getChatMembers(chatId);
if (localMembers) {
const localMap = new Map();
localMembers.map(member => localMap.set(member.phone_number, member.seald_id));
snapshot.docs.map(doc => {
const localSealdId = localMap.get(doc.id);
if (localSealdId !== doc.data().sealdId) {
discrepancies++;
}
});
}
return { discrepancies, data: snapshot.docs.map(doc => ({phoneNumber: doc.id, sealdId: doc.data().sealdId})) }
}
This is known as the “Trust On First Use” (TOFU) paradigm. As soon as your Freedom Chat app has interacted with another user, it stores sufficient information locally (on your device) about that user to detect potential future attacks. This makes attacks much more difficult in practice, as they become easier to detect. We will cover how to detect an attacker attempting to swap the recipient’s keys before first use below.
Encrypting A Chat in Freedom Chat
To encrypt a chat, a Freedom Chat user generates a “symmetric key” randomly and encrypts it using the recipients’ public keys. The encrypted symmetric key is sent via a server to each recipient, who can then use their own private key to decrypt it and recover the symmetric key.
// sender
const session = await seald.createEncryptionSession({ sealdIds: [ /* list of recipients' sealdIds */ ] })
// session.id is sent from sender to recipient via Freedom Chat's servers
// receiver
const session = await seald.retrieveEncryptionSession({ sessionId: session.id })
Now that all participants have a shared symmetric key, they can encrypt messages using a symmetric encryption algorithm. The one we use is AES-CBC combined with an HMAC-SHA256 as detailed in [`sscrypto`’s `SymKey`]
(https://github.com/seald/sscrypto/blob/b360e47eb4e99e8fd363ea880db8191cd3a45af1/src/node/aes.ts#L7).
// sender
const encryptMessage = async (session, message) => session.encryptMessage(message)
const encryptedMessage = await encryptMessage(session, message)
// encryptedMessage is sent from sender to recipient via Freedom Chat's servers
// receiver
const decryptMessage = async (session, encryptedMessage) => {
try {
return await session.decryptMessage(encryptedMessage)
} catch (error) {
console.warn("Could not decrypt message: " + error)
// This may happen if the user is not a legitimate recipient
}
}
The same applies to images or videos, although they are encoded differently to ensure that the file name is also encrypted. An encryption session is rotated whenever a user is added or removed from the chat to maintain forward secrecy.
Checking The Encryption Keys
const sigchainHashes = {};
await Promise.all(users.map(async (user) => {
const getRecipientsResult = await seald.utils.getRecipients({ userIds: [user] });
if (getRecipientsResult.hairlessRecipients.length > 0 || getRecipientsResult.recipients.length !== 1) {
throw new Error('retrieved invalid recipients');
}
const recipientSealdId = getRecipientsResult.recipients[0];
const sigchainHash = await seald.getSigchainHash({ sealdIds: [recipientSealdId] });
sigchainHashes[user] = `${recipientSealdId}:${sigchainHash.hash}`;
}));
const stringifiedSigchainHashes = anotherjson.stringify(sigchainHashes);
const totalHash = seald.sscrypto.utils.sha256(Buffer.concat([Buffer.from(stringifiedSigchainHashes, 'utf8'), encryptionSession._sessionSymKey.key, Buffer.from(encryptionSession.sessionId, 'utf8')])
When all the users in a chat verify that they have the same QR code, they can be sure of the following:
- There is no unauthorized participant in the conversation who could maliciously read the messages.
- Every participant has the expected signature chain hash, meaning the expected public key (everyone is who they claim to be).
If verification is completed before the first message is sent (by selecting “verify end-to-end encryption” at the top of each chat), any man-in-the-middle attack is entirely prevented.
No Storage of Messages on Our Servers
As soon as any message—whether it includes images, videos, or GIFs—is successfully delivered to its intended recipient, it is removed from our servers and managed solely on the user’s device.
Did You Know?
Our encryption technology complies with today’s and tomorrow’s regulations, like HIPAA, GDPR, CCPA, PCI DSS and more. Freedom Chat also prevents screenshots and media from being saved without explicit approval.
Freedom Chat End-to-End Encryption Summary
Freedom Chat’s end-to-end encryption ensures that only authorized users can access their conversations—not hackers, app administrators, government institutions, even service providers.
If security code verification is completed before the first message is sent, any man-in-the-middle attack is entirely prevented.
Freedom Chat has no commercial use of user data (unlike WhatsApp, Viber, or Telegram) and we protect your personal information with the strictest security protocols.
Thank you for being a Freedom Chat user. If you have any feedback or see room for improvement, feel free to contact us at feedback@freedomchat.com.