This is a follow up on my previous question about Storing sensitive, sharable data for a company where several of the employees have access to the server.
This solution has worked well for our customer, and we're looking to make a v2.0 as a SaaS. We still think that the added security of encrypted data is worth while even when server access is limited to our staff.
The first version had encryption/decryption done by the PHP application, but v2.0 has implemented OpenPGPjs library in a Single-Page Application (SPA) talking to a backend application (API). All encryption and decryption is now done in the browser, and this helps to mitigate the threat of memory dump attacks and adds an extra layer on the network traffic (this is of course also running with HTTPS).
The first version also had two separate passwords, one for logging into the application, and one that was only the key to unlock the private key. Much like ProtonMail's original solution. We have, inspired by ProtonMail, also adopted a single-password login to our service - the current user process is as follows:
(I've used strong emphasis capital letters to define notations)
1. Registration
The user enters e-mail-address and password in the a form in the SPA, and the SPA hashes the password (PASSWORD) with bCrypt. The registration form is sent to the API with e-mail and password hash (DIGEST).
2. Application Authentication (Login)
The user enters their credentials, and the SPA sends a pre-auth request to the API. The API answers with the salt of from the DIGEST (technically its $algo$rounds$salt
) and the SPA uses this to hash the PASSWORD and send the e-mail and DIGEST as an authentication request. (the plaintext PASSWORD is never sent to the API)
If the authentication is successful, the API adds two http_only cookies in the browser. One CSRF-Token, and one signed JWT-token. The API send a response to the SPA including the CSRF-token.
(All successive requests are made with the CSRF-Token in the HTTP headers, and the API is comparing the token in the cookie with the token in the header)
3. Key Generation
When the user first log in, they are prompted to generate a EC public-private keypair. They re-enter their PASSWORD, and this is then hashed with a newly generated bCrypt salt. The new password hash (KEY) is used to encrypt the Private Key of the newly generated Keypair (KEYPAIR) (also generated in the browser). The salt for the KEY, public key and encrypted private key are then sent to the API and stored in a database.
4. Encryption/Decryption
When the user stores sensitive data in the system, the data is encrypted with AES256 with a randomly generated key (ENTRY_KEY) and sent to the API. The ENTRY_KEY is encrypted with the user's own public key, and stored as a token for that user to that entry. If the user wants to share that entry with another user, they decrypt the ENTRY_KEY and encrypts it with the new users public key. Now both users can decrypt the entry with their own token, and updates to the entry will be visible to both users.
Currently the KEY is stored in LocalStorage with a 5 minute expiration. The KEYPAIR (with the Private key encrypted) is stored in LocalStorage without expiration. When the KEY is expired from the LocalStorage, the user is prompted to re-enter the PASSWORD.
5. Password Resets
Someone will forget their password. It always happens.
We can reset the authentication password with standard forgot-password-functionality, but information encrypted with the keypair cant be restored - unless its shared with someone, then they can request to get those entries shared back to them.
Questions:
- Are there any security concerns with using the same password for both encryption and authentication? In a breach the attacker could possibly get two variations of the password (DIGEST and KEY), hashed with different salts. Any alternatives to this approach?
- Storing the KEY in LocalStorage feels wrong, but we also can't prompt the user for their PASSWORD for each action in the SPA. Is local storage entries deleted permanently on expiration, and is there any safer storage options?
- Any other flaws you might see? Application source code attacks and compromised client systems are concerns that we're looking at, and any input to strengthening the security of the application is appreciated.
This was a long post, and I did my best to explain as good as I possibly can, but if there are anything that are unclear - please add a comment and I'll update.