I am looking for some compromise with integrating E2E encryption for web app (basic FE and BE).

Basically I read about two concepts which are truly E2E encryption and not that much complicated.

  1. Using two passwords, when one is for login and second is for encryption only used only locally on client device. Something like Protonmail used before.
  • pros: secure, bulletproof?

  • cons: too much hassle with having two passwords, risk that users will use same password

  1. Using only one login password which is used also to encrypt data. To prevent server reading you send only hash of this password to server to verify identity. This is the current Tutanota email service setup.
  • pros: only one password, simple
  • cons: resetting password would lead to data lost as those old data can't be decrypted?
  • Do you have an actual question to ask? It looks like you already done the research, so YOU need to make a decision on what to choose.
    – musefan
    Sep 15, 2020 at 20:54
  • Tutanota docs say they salt and hash passwords at the client side, so the server just checks hashes on login to see if they match the ones stored at registration. If an attacker manages to obtain any of those hashes, he could impersonate users without the server noticing. The salt would prevent the attacker from breaking the password with a dictionary attack though. May 17, 2022 at 16:35

2 Answers 2


Having the authentication password by the same as the encryption password by default but allowing the user to set a different encryption password might be the best of both worlds. The data is always going to be lost if the user forgets their encryption password (you can have them download and maybe print out a backup key as a safeguard against this), regardless of whether or not that encryption password is also the authentication password. However, changing the encryption password works fine; you decrypt the master key with the old password-derived key, derive a new key from the new password, re-encrypt the master key with the new password-derived key, and upload the re-encrypted master key back to the server).

One "gotcha" here is that, while client-side hashing is quite fast now (fast enough to make it practical to use a strong key-derivation/password-hashing function twice during login, once for authentication and once for the decryption key), it's hard to have an unpredictable salt. You can use a unique but predictable salt (such as the user's username), but that makes it easier to pre-compute attacks against a specific user or set of users. You can have salts served for arbitrary usernames without authentication, but that's also vulnerable to pre-computing against specific users.

Since the thing you really care about securing is the encrypted data, one option is to have the login flow look like this:

  1. Client takes user's password (for login, if they're different), salts it with their username, and applies a password-hashing function (such as scrypt or argon2).
  2. Client sends the username and hash output to the server as a login request.
  3. Assuming the user has an account on the server, server retrieves a securely random salt (unique to that user) from its database and re-hashes the client-supplied hash with the user's salt, again using a secure hash function (essentially stretching the work factor of the hash function somewhat).
  4. Server uses the re-hashed result to authenticate the user, ideally also performing a second-factor authentication check (such as via push notification, FIDO2, etc.).
  5. Assuming the user authenticates successfully, the server sends the client back the following info:
    • The user's encrypted data blob.
    • The user's encrypted master key.
    • A second per-user salt, distinct from the one used for authentication, that is used to derive the decryption key.
    • A flag telling the client whether to re-use the login password for deriving the decryption key, or request an additional password.
  6. The client takes the server-supplied salt plus the decryption key password (whether the same as the login password or not) and uses a password-hashing / key-derivation function on them, to produce the user's current password-derived key.
  7. The client uses the password-derived key, possibly in combination with another key (such as on a flashdrive), to decrypt the master key, then decrypts the encrypted blob using the master key.
  8. The client immediately purges the passwords and password-derived key from memory, after decrypting the master key. 9 The client can purge the encrypted master key, server-supplied salt, etc. from memory after using them to decrypt the master, or cache them (such as in local storage) for offline use (using only the decryption password, not the authentication password); whether or not such offline functionality is desirable (against the risk of storing the user's encrypted data and salt in more places) depends on the application and potentially the user.
  9. The client purges the decrypted master key and any decrypted parts of the blob, along with the authenticated session token, immediately upon logout / session expiration.

The advantage of this scheme is that no one or even two parts (assuming the passwords are different) is useful without the rest. Having both the login and decryption password (which might be the same!) without the second factor won't get you the blob or the stuff needed to decrypt it. Having the decryption password and blob isn't useful without the encrypted master key and the decryption salt. Having the authentication password and second factor (but not the decryption password, assuming it's different) gets you the blob but you can't decrypt it.

This also lets you rotate passwords easily (and even reset the authentication password without data loss, though resetting the encryption password prevents ever decrypting the old data). It easily supports adding additional authentication and/or decryption factors (hardware tokens, flash drives, U2F/FIDO devices, certificates, TOTP or push-based authenticator apps, etc.). It supports backup decryption keys and backup vault copies. It is practical with or without separate authentication and encryption passwords. It doesn't even require passwords at all; for example authentication could use TLS client certificates, and decryption could use a local keyfile or an encrypted key that must be decrypted with a private (asymmetric) key stored on the device or on a hardware token. Finally, because the authentication password is already hashed with a strong password hashing function before the server even sees it, the server can't figure out the user's password (even if the same password is used for decryption) without very expensive brute-forcing. An attacker could start brute-forcing the hashes for individual users, but even though the salt is predictable and probably not extremely long, it's unique for every user so this would be expensive in general.

With all that said... the CRITICAL weakness of such a system implemented as a web app is that there's no practical way for the user to know that the system will do the right thing each time! Because the client (that is, the JS that implements all of this) is downloaded from the server every time the app is used (at least, is used online), the user must either audit all the supplied JS every time, or just trust that there are no harmful additions (such as something that logs the user's passwords, or the decrypted master key, or so on). In other words, a malicious server admin could instantly and totally compromise somebody's account as soon as that user signed on.

To avoid that risk, you need to provide the user some way to run a client that they know is trustworthy and hasn't been changed since it was verified. Browser extensions can theoretically do this, but many browsers automatically update those (without letting you control the update behavior) now. Thick clients - either officially open-source or at least script-based so they're easy to analyze - are another option, but really don't work for a web app (and can be difficult to install a verified and non-automatically-updating version of on mobile).

Finally, consider that web apps come with all the web app security baggage. Because you're storing the secrets (decrypted master key, blob, etc.) in JS, an XSS vulnerability would compromise the user entirely. CSRF or clickjacking could delete the user's account off the server. Malicious browser extensions with sufficient privileges to inject scripts into the site could fully compromise the victim. Additionally, there are other risks from using a JS-based client. JS has a number of unfortunate limitations from a security perspective, such as no way to reliably zero memory or access OS- or hardware-based secure key storage. The language itself is geared more toward ease and speed of development rather than security (see everything about its typing system, "this" keyword, etc.), which makes it harder to catch certain bugs. You don't really get to pick the crypto implementation either - even if you send your own primitives instead of relying on whatever the browser provides, it'll run on the user's JS engine through a JIT compiler that you can't control - so the risk of side channels is going to be higher than normal.


It's not clear what you are really asking.

  • From an usability point of view, requiring a single password is preferable.

More than "too much hassle" I would describe it as "users being confused". I wouldn't consider users using the same password for both fields as a failure in the schema. Plus, you could always make the app not allow using the same password in those two fields on registration.

  • On the other hand, from a security point of view, it's preferable to use two passwords completely unrelated.

First of all, note that in the use-same-password-scenario you clearly want to use different password derivations, so the two different operations cannot actually be linked (suppose that the first step of the local encryption started by hashing the password in the same way as for authentication, oops!). You could perhaps use two HMAC with different keys, or PBKDF2

Even then, having something that can be used to validate the data password could be considered a weak point. Suppose a dictionary attack, the attacker checks if it would be possible to use the login-algorithm to log in with the password "Password1". It succeeds. Then then attacker knows that Password1 using the encryption-algorithm would recover the data.

You mention as a con that resetting password would lead to data lost as those old data can't be decrypted. But that's already the case in the prior scenario. If the user loses the data encryption key, he won't be able to access his data.

  • An intermediate solution might be to provide both options. By default, use a single password for both actions, but let an advanced setting let the user set a second, different password, for encryption.

That way you could please those that wanted a separate encryption password while not bothering too much those users that would be confused / not benefit from it, such as your typical that would use Password1 and Password2.

  • Finally, I would like to question your premise that you want to use passwords.

What's your need for passwords? It would be more secure to use certificates. If the user registers using an application, it creates both secrets and handles them separately. In order to enroll a different client, a backup option produces two files (or a single file combining both secrets) with the relevant files.

A password might be "simpler" to backup (there is the chance that the user would remember it with no copies), but produces weaker security.

You mention that it is a "web app". If the user needs to authenticate every time on a web page, it may be simpler to introduce 1 or 2 passwords rather than uploading a password file. Webs are quite bad framework for E2E applications, though, since the attacker could provide a malicious web page that stole your data or encryption. In that sense, a native application would be preferable.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .