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.