End to end encrypted web chat app with stored messages in database 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:


User X creates an account in his browser.
A key pair is created and the private key is encrypted symmetrically using the entered password.
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:


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.

Both encrypted version of the messages are transmitted via WebSocket to the server.
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.
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:


The user logs in and receives the encrypted private key. Using the entered password, the private key is decrypted.
The already existing chat messages are requested from the server.
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.
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 &quot;raw&quot; 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).