I want to build an app that uses client-side encryption for storing encrypted data on the server. A user-specific master key would be used, so to easily share it between devices a encrypted version of it would be stored on the server.

The user's passphrase would be used for both 1) encrypting the master key and 2) authenticating the user. For this reason the server must not know the passphrase.

The passphrase would be used twice for stretching with PBKDF2, HMAC-SHA256 and a good number of iterations:

First, the encryption key used to encrypt the master key would be derived. A random salt would be used here (also stored on the server with the encrypted key).

Second, a hashed version of the passphrase would be derived. The user email would be the salt.

The stretched passphrase (#2) is then sent to the server instead of the plain passphrase. The server would then use bcrypt to further secure that hash and authenticate the user.

Assuming that all communication happens over HTTPS and the user is forced to pick a strong passphrase, I don't think there is anything wrong with this. Am I wrong?

3 Answers
3

This is far from secure, assuming a passphrase that a human can remember. The main thing you have to note is that an offline brute force attack on the password can be carried out. This is because the server can guess the password and follow the same procedure and see if decryption works. It is possible to buy a machine that computes billions of hashes a second (for a few thousand dollars). Very few passwords will be strong enough to resist such an attack.

If you can't do anything better than encrypting with a password, then at least it's something. But I wouldn't trust such a solution (not for passwords that I can remember). I suggest finding a different way to propagate a secret key for the client, rather than storing it on the server. (I know that this will be less usable, but security costs...)

I think the idea is good - it is pretty much what Lastpass does (see below). But as with anything to do with cryptography every detail matters. If you're using email for the client salt it seems like a good idea to run the email through PBKDF2 first to ensure sufficient entropy.

Although there's speculation there might be vulnerabilities when you do a double HMAC i.e. HMAC(HMAC(..)..) in some scenarios, in practice with the high number of PBKDF rounds I doubt it will be an issue. IMO coupled with a high number of server rounds this authentication scheme is rock solid.

EDIT 7/2/2015

This is partly to comment on Yehuda Lindell's answer above (I'm not able to add comments yet), and present an alternative suggestion to PBKDF2 usage:

One option is to rely on both the client and the user. PBKDF2 accepts a password and salt. Instead of email as original poster suggested, randomly generating the salt on the client will ensure a client side component. Unfortunately, this will lock you down to a specific client.

$\begingroup$For code spanning several lines, I recommend indenting it with 4 spaces, instead of using a <code> html tag.$\endgroup$
– CodesInChaosJul 1 '15 at 20:30

$\begingroup$I want to stress that the use of passwords for authentication and their use as a key to encrypt are fundamentally different. For authentication, it would be nice to do other things, but I think that they are still here to stay for a very long time. This question related to their use as keys in encryption, and this is very very problematic and should be avoided whenever possible.$\endgroup$
– Yehuda LindellJul 2 '15 at 17:50

So, there's a master key, presumably randomly generated in the client, never shown to the server, call it $k_m$. Then there's an encryption key, derived from a user passphrase and a (random) salt:

$$k_e = \operatorname{PBKDF2}(p, s)$$

There's also an authentication key, derived from the same passphrase, but a different salt (email address):

$$k_a = \operatorname{PBKDF2}(p, e)$$

The server knows the encrypted master key and a hash of the authentication key:

$$(E_{k_e}(k_m), H(k_a))$$

The question is whether this is secure.

The server cannot use knowledge of $k_a$ to derive $k_e$, so that's fine. An attacker can produce neither $k_a$ nor $k_e$ without knowing the passphrase $p$, so that's fine. An attacker cannot even verify guesses for $p$ offline, without gaining access to the data on the server or breaking TLS, which is great. (The last assumes neither $k_a$ nor $k_e$ is used for anything else.)

However, there are some lesser weaknesses or issues.

First, since PBKDF2 is used twice with different salts, the client takes twice as long to derive $k_a$ and $k_e$. If you used PBKDF2 once to derive a password hash, then derived $k_a$ and $k_e$ from that, you could lessen the computational cost or increase the number of iterations.

Second, email is... not a very good salt in my opinion. It can change (or should be allowed to!) if the user changes to a different address, necessitating a recalculation of all the hashes and encryption. It could also clash with some other service or app that happened to use a similar scheme, allowing simultaneous cracking of passwords/phrases for both. Finally, it cannot be easily changed when the passphrase changes, which is a good idea to reset any potential attacker's computations.

Neither of these is enough to consider the scheme insecure, but in my opinion could be done better.

$\begingroup$Agreed. At a minimum, PBKDF2 should be used to stretch the user-provided passphrase with a random salt, then HDKF can be used for deriving the encryption key and the password verifier (using a different info string for each). Once the verifier is sent to the server, it can simply be hashed once (SHA-2, BLAKE2b, whatever) before being stored.$\endgroup$
– Stephen TousetJul 2 '15 at 0:02

$\begingroup$@StephenTouset Coming up with a random salt is not the issue here but where to store it. The reason the OP and the LastPass Team resorted to using the email as salt is most likely that they wanted to avoid having the user remember/write down/print out some kind of recovery sheet containing the salt.$\endgroup$
– Oliver WeichholdFeb 7 '17 at 9:27

$\begingroup$There is no need for a user having remember, write down, or print out a long random string in this scheme. The server can store the salt and provide it to clients upon request when given the email address or username.$\endgroup$
– Stephen TousetFeb 7 '17 at 19:07