1
$\begingroup$

I want to generate an encryption key for the user to encrypt their data locally before sending to the server. The server should not have any knowledge, nor be able to compute, this encryption key.

This encryption key should be deterministic, so that regardless of which device they're on, the same key is generated and stored locally.

This makes their password an ideal choice to generate a key from.

At first I thought, easy:

Given user inputted password uip:

encryption_key (ek) = sha256(uip);
password_sent_to_server (pw) = sha224(uip);

This way, the server can never know or receive the user's inputted password, and cannot compute the encryption_key.

However, after asking this question, I've been told to use a key derivation function instead.

The problem is, I'll now need two random salts to generate ek and pw. And these salts cannot be stored on the server. So that means I'll also need to generate two deterministic salts based on either the user's email or password.

What's the right way to go about doing this?

This answer recommends using the email as the salt. So perhaps:

ek = pbkdf2(uip, email, 1)
pw = pbkdf2(uip, sha256(email), 2)

(The sha256(email) for the second salt is questionable. Was just trying to get at a different non-random salt for the second one.)

This answer recommends using pbkdf2 to generate a 512 bit key and splitting it:

output = pbkdf2(uip, email, 1, 512);
ek = firstHalf(output);
pw = secondHalf(output);

Am I interpreting these answers correctly? Any commentary on either approaches?

$\endgroup$

2 Answers 2

3
$\begingroup$

Neither approach is particularly good because it depends too much on the PBKDF2 function. The PBKDF2 function should always be performed in such a way that the number of iterations (the work factor) is high.

Besides that, PBKDF2 should not be asked for more output than the internal hash function (usually SHA-1, so 160 bits / 20 bytes). Otherwise you give back a part of your advantage back to the attacker (this is an unwanted property of PBKDF2 itself).


You can perform the following on the client:

master = pbkdf2(uip, email, 2000, 256);
hash = hkdf(master, 'hash')
kenc = hkdf(master, 'kenc')
ciphertext = gcm-aes(kenc, message)

then send hash and ciphertext over a TLS encrypted channel to the server (obviously you do not want to leak the hash value to an attacker).

The server should contain a hash over the previously calculated hash value, storedHash = H(hashOrg). It can then do a (secure) compare with the calculated hash value calculatedHash = H(hashGiven) to perform authentication.


Because hash cannot be reversed to the master value (which is pseudo-random, with the strength of the password and the work factor combined), it can also not calculate kenc.

The additional hash on the server just puts a bit of distance between the hash send over the secure channel and the stored value. Otherwise retrieving the database is enough for an attacker to obtain hash and simply send that value to log in.


Note that PBKDF2 is computationally intensive. You can bring that down by choosing really strong passwords. You could also use an asymmetric key pair, pasting in the public or private key value when required.


The biggest issue is that you'd still need to trust the service, especially if you would use encryption in the browser. That is not something that is easily avoided. You can avoid some of this by having your server audited or by creating an open source client application and only put the code into a repository after a thorough review. But at some point the client will still have to trust you, the service provider. There is no known good way around that.

Your client application may implement a great encryption / authentication scheme and send the password together with the authentication code and authenticated ciphertext. Simple as that.

If your users wants encryption that cannot be decrypted by the service, he/she is better off encrypting with their own PGP public key, and pasting the result ASCII armored into some input form.

$\endgroup$
1
2
$\begingroup$

Well, why can't you send the salt to the server ? Salt isn't secret [1]. It's just one protection from same password being used by more than one user, or rainbow tables, or any other case where the same password would generate the same hash.

The recommendation about using a key derivation function is to slow down any offline attack, as it adds complexity. Or, in another use, you can derive two keys from the same password, as you stated.

But, in your case,

ek = pbkdf2(uip, email, 1)
pw = pbkdf2(uip, sha256(email), 2)

So that the ek is one key and pw is another key, both using the same password uip and the same salt email, as anyone who has access to your code will know that just doing a sha256 he gets the second "salt". Since salt isn't secret, you can use whatever you want, email being possible as you stated.

Then... I didn't get what's your question.

[1] https://en.wikipedia.org/wiki/Salt_(cryptography)

$\endgroup$
1
  • $\begingroup$ Comments are not for extended discussion; this conversation has been moved to chat. $\endgroup$
    – e-sushi
    Commented Jun 23, 2017 at 1:57

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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