I want to implement end-to-end encryption (short: e2ee) in a chat app I am building. I spent the last few days researching the topic and there are still a couple of questions left that I wasn't able to resolve.
Question 1: Implement a group chat
The idea for e2ee for just 2 chat participants (let's say Alice and Bob) is pretty simple. Alice and Bob generate public and private keys and store the private key on their device and the public key openly in the database. When Alice wants to send a message to Bob, she gets his public key, encrypts the message and only Bob can decrypt it using his locally stored private key. So far so good.
Let's say Charlie enters the chat room. Now Alice wants to send a message that is readable by both Bob and Charlie. And this is the problem I am stuck on. I thought about 2 possible solutions but they seem not ideal.
- Alice actually sends the message twice. One encrypted with Bob's public key and the other encrypted with Charlie's public key. Then Bob and Charlie just have to decrypt their respective message. The obvious drawback is that if you have 100 people in a chat room you have to send a message 100 times, which generates a ton of redundant data. And a message could be arbitrarily large.
- Alice encrypts the message using a newly generated key symmetrically and sends the encrypted message to Bob and Charlie. Additionally, she encrypts the message key with Bob's and Charlie's public keys and also sends that. Now Bob and Charlie can fetch the message key by decrypting it and using that information to get the message content. I think for big chat groups, that is the better solution because although you have to send multiple encrypted message keys, they are capped in size contrary to the messages themselves. But it is still a ton of data per message.
What is the standard way of doing it? Maybe you could use my version 2 but use the same symmetric key for multiple messages but how to handle people leaving and joining the group?
Question 2: Restoring the private key when using oAuth and resetting the password
During my research, I came across this talk speaking about something called a brain key. The idea actually blew my mind. In short (how I understood it): When a user registers on my site they generate a key pair. The user also has to specify a password. Now the device calculates the brain key by symmetrically encrypting the private key using the clear-text password as the key. Now the hashed password, public key, and brain key can be stored safely in the database. When the user loses their device and logs in on a new one, first the password gets verified. If it is correct, the clear text password can be used to restore the private key. When a user changes its password they have to specify the old one. Now the same process can take place by verifying the password, restoring the private key, and then setting a new brain key using the new password. Pretty smart, isn't it?
But what happens, when I want to enable the user to reset the password, eg. with a link sent by e-mail. Then I have no way to get the private key because the user doesn't deliver the old password. The only exception would be if the user performs the reset on a device, where he already used the app. So there might still be the private key stored somewhere. But I definitely shouldn't rely on that.
The other problem is that I want to include oAutch in the app but I don't get access to the clear text password at all. What I would need is some kind of string that is only available during login and stored nowhere (or maybe just on the third-party database) but still the same at every successful login (Just like a password). Is there such a value that I can retrieve using oAuth?
One solution for that would be to add a pin code for every user. I would tell the user something like: "Hey if you want to be able to restore messages on other devices, you have to remember that pin code". And then use that pin code instead of the clear-text password to encrypt the private key to generate the brain key. And of cause, I would only store the hashed pin code on the server. But this doesn't feel right as users have to essentially remember 2 different passwords for one account. What do you think?
I am using the pretty common react-native + Firebase combo, so maybe there is already an open-source solution for all of that.