2
$\begingroup$

I am working on a web app with an integrated chat. All chats can only occur between two users - not more, not less.
I am very concerned with security, so I would like to encrypt all chat messages with end to end encryption. My favored protocol so far is the Signal protocol.

However, the problem of web apps is that it's not possible (as far as I know) to reliably store the private key of a user. Also, you can't really save anything locally (i.e. in the local storage of the browser) because the web app can be accessed from everywhere and the user should be able to view all messages as soon as he logs in wherever he might be.

So my solution would be:

  1. User X creates an account in his browser.
  2. A key pair is created and the private key is encrypted symmetrically using the entered password.
  3. The public and the encrypted private key are sent to the server where they are stored in the database.

Then, if a user Y wants to start a chat with X:

  1. Y's message is encrypted in his browser two times:
    • Once with the public key of user X, stored in the database,
    • and then with the password of user Y.
  2. Both encrypted version of the messages are transmitted via WebSocket to the server.
  3. The server sends the first version of the message to user X if he is currently logged in. Also, it stores both versions of the message in the database.
  4. The browser of user X decrypts the message(s) received from the server as soon as he is logged in and displays them to the user.

Each time user X logs in and views the chat:

  1. The user logs in and receives the encrypted private key. Using the entered password, the private key is decrypted.
  2. The already existing chat messages are requested from the server.
  3. Locally, the messages are decrypted with the private key.

Is this safe at all?
I think that this would prevent some of the following disadvantages when researching other methods:

  • The server does not have to decrypt and re-encrypt all messages when a user changes their password.
  • The browser does not need to generate a new key pair each time a user logs in at a new location.
  • The messages can be stored long term for both users to be easily read.

One potential vulnerability I thought of would be:

  • Because there are two versions of the message stored in the database, a potential attacker could have an easier job of brute forcing the original content. Though, this might be prevented by signing both messages with some userspecific value, i.e. the username.
$\endgroup$
1
$\begingroup$

Just keep all user-associated information encrypted using the user's password. Be sure to use a secure password-based key-derivation function (PBKDF2 seems to come to mind but personally I prefer Argon2) because passwords make terrible encryption keys. Here is an example of how it might be done:

  • User generates random encryption key (master key or MK) on the client-side (not based on a password, actually random)
  • User generates a key-encryption-key (KEK) from their password
  • User encrypts MK using the key KEK
  • User uses MK to encrypt their data, within their browser.

This way, if a user is to change their password they can simply re-encrypt MK with a new KEK. Or, they have the option to change their MK and KEK if they think their MK has been compromised. You don't need anything more, since the encryption does the work. Everything else can be done as if client-side storage is available, since it's secured with MK.

This sounds mostly like what you're doing, but it doesn't sound like you're using a KEK or key-derivation function, both of which should be the case if the system is to be secure and scalable.

A key pair is created and the private key is encrypted symmetrically using the entered password.

Encrypt with MK, not the password

Y's message is encrypted in his browser two times

Because there are two versions of the message stored in the database, a potential attacker could have an easier job of brute forcing the original content. Though, this might be prevented by signing both messages with some userspecific value, i.e. the username

This doesn't affect security, assuming you do it right. Each message should be encrypted with a random IV for this to work. Make sure it's random, and unique to each message. This will double the amount of information you need to encrypt, but anything else is insecure. The IV can be known by the server, without compromising security, and as such, needs not be encrypted.

Keeping "raw" password in browser as a key for symmetric encryption/decryption maybe a concern.

This is correct. Definitely derive the key from the password.

Passwords are limited in length, and may not provide crytographic high strength if used as symmetric key directly. One option could be to derive the key from it

Definitely do this! Use a function specifically designed to derive encryption keys from passwords.

var key = crypto.createCipher('aes-128-cbc', 'secret password');

This is a bad idea (sorry, but it is). AES isn't a key-derivation function. Please don't use AES to derive keys! use PBKDF2 or Argon2; they're specifically made for exactly that purpose.

This approach wont allow server-side search on the chat messages. Client side search can be implemented on a limited text loaded into UI component.

That is correct. However, if I were the one using the service I'd expect that; I'd automatically distrust anything that allowed server-side search.

The server does not have to decrypt and re-encrypt all messages when a user changes their password

This can only be done if you use the MK-KEK scheme I described. I should also add, the client should do the encrypting and re-encrypting, not the server.

Changing the password means you have to at least re-encrypt something. Also, if the MK is compromised you still have to re-encrypt everything. Be ready for that, or risk sacrificing security (which to me is an unforgivable sin).

| improve this answer | |
$\endgroup$
  • $\begingroup$ I just realized this question is 10 months old... Oops! I guess this answer is going to /dev/null. Or maybe the rest of the community can benefit from it. $\endgroup$ – Serpent27 Sep 8 at 20:57
  • $\begingroup$ The question might be 10 months old and I put the project on hold for now but it will be useful none the less! I actually used PBKDF2 in the project eventually, though not as a basis for a asymmetric encryption as described. Instead I created a much less privacy-concerned, symmetric encryption which does not completely prevent the server owner from reading all the messages. $\endgroup$ – KingOfDog Sep 10 at 19:09
0
$\begingroup$

Sounds like an elegant solution to me.

A few points, not necessarily a criticism:

1) Keeping "raw" password in browser as a key for symmetric encryption/decryption maybe a concern.

2) Passwords are limited in length, and may not provide crytographic high strength if used as symmetric key directly. One option could be to derive the key from it:

var key = crypto.createCipher('aes-128-cbc', 'secret password');

3) I do not think that encrypting by username below is any better than using a server side common symmetric key. Attacker could crack username as easily as the server-side common symmetric key - just that common symmetric-key, makes the code organization easier IMO.

Because there are two versions of the message stored in the database, a potential attacker could have an easier job of brute forcing the original content. Though, this might be prevented by signing both messages with some userspecific value, i.e. the username.

4) This approach wont allow server-side search on the chat messages. Client side search can be implemented on a limited text loaded into UI component.

| improve this answer | |
$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.