2
$\begingroup$

I need to derive two keys from a single password client side. One for in-browser login, the other for encryption.

Is the following secure ?

login key      = pbkdf2(password, salt1, lowN)
encryption key = scrypt(password, salt2, highN)

Explanation:

  • Server shouldn't know plain password
  • salt1 is retrieved from server upon login
  • User logs in using login key, download data + salt2, then decrypt it using encryption key client side.
  • I would love to just use scrypt (stronger), but the only vetted key derivation JS library I found is pbkdf2 by SJCL. Plus I need user to be able to login to website from mobile devices.
$\endgroup$
5
  • $\begingroup$ PBKDF2 with SHA512 can generate enough key material for both. However I would recommend SRP for login $\endgroup$ Feb 25, 2015 at 9:31
  • $\begingroup$ SRP is patent-encumbered $\endgroup$ Feb 25, 2015 at 11:34
  • $\begingroup$ I thought the entire point of SRP is that it worked around patents $\endgroup$ Feb 25, 2015 at 11:36
  • $\begingroup$ Inline with your suggestion, I found this and this. Not sure if KDF can be substituted with any KDF though (e.g. pbkdf2 and scrypt). I'm prepared to go with pbkdf2 only if it's more theoretically sound. $\endgroup$ Feb 25, 2015 at 11:42
  • $\begingroup$ J-PAKE is SRP without patent. It's hard to find stable implementation though. $\endgroup$ Feb 25, 2015 at 11:47

1 Answer 1

2
$\begingroup$

Server shouldn't know plain password

If the user types their password into a web page in a browser, the server that hosts that web page always has the opportunity to see what password was entered. There's no cryptographic mechanism that can prevent that (at this point in time).

To get around this limitation, you need to build a downloadable application instead (e.g. you could use NW.js and Cordova to package up HTML/JS/CSS into desktop/mobile apps). If you don't do this, there's essentially no point in trying to hide the password from the server. More about that in this article.

If you've done that (again, no point in continuing if you haven't), I would recommend against using a JS implementation of PBKDF2 or scrypt, if possible. A JS implementation of either will be slower and force you to use a lower iteration count (since users will only wait so long). Attackers will have access to a highly optimized C implementation, so this would give your attackers a significant advantage. Note that most languages have wrapper libraries for C implementations of PBKDF2 or scrypt (e.g. node-scrypt).

To derive the keys, I recommend taking the following approach on the client:

  • Derive one 256-bit master key using scrypt (PBKDF2 is also acceptable). Use security parameters that are as high as practical, but keep in mind that mobile devices might have a rough time. I recommend using a unique salt for each user.
  • Use HKDF-SHA256 to derive a login key. HKDF allows you to safely derive keys from a master key such that it is infeasible to determine the master key (or other keys) from the derived keys. In the context or info parameter, use the string "server-login-key". 128 bits of output should be adequate.
  • Use HKDF again to derive an encryption key. Be sure to derive the encryption key from the master key, not the login key. In the context/info parameter, use the string "client-encryption-key". Generate a key long enough to match whatever encryption algorithm you plan to use (e.g. 256 bits for AES256).

When storing the login keys on the server, be sure to treat them like passwords (store only the salted hashes). You may use PBKDF2/bcrypt/scrypt here again, but it's not critical since the key has already been stretched. Additional stretching is a good thing, but it won't help if the server is actually malicious.

An important difference between this and your design is that it invokes PBKDF2/scrypt only once, instead of twice. This allows you to double the security parameter (i.e. one invocation with 2N iterations instead of two invocations with N iterations each).

If users don't have strong passwords, then the server will be able to crack the password based on the login key (dictionary attack), so give careful thought to this as well.

Note: The "Extract" step of HKDF is optional in this case because PBKDF2 and scrypt already output cryptographically strong keys, but it won't hurt.

$\endgroup$
4
  • $\begingroup$ Thanks for your thorough answer!I'm leaning towards that direction now (scrypt once in native app without HKDF, just ask scrypt to generate len(login+encryption)). However, this means that my users won't be able to login through website (UX flaw). Do you have any idea how to solve that ? I'm currently thinking from UX point of view: During website login, ask user to install app to login. If they don't want, notify them that password will be sent in plain (do expensive scrypt server side). $\endgroup$ Feb 27, 2015 at 6:38
  • $\begingroup$ I even look at Password Hashing Competition to see if there is something I can use in the future. Makwa looks interesting (splitting work client-server), though it's still doesn't solve the trust problem in JS code delivery. $\endgroup$ Feb 27, 2015 at 6:40
  • $\begingroup$ If users logging in through the website don't need to access their encrypted data, then there are a few possibilities. (1) give users two separate passwords. (2) use the same password for both the website and the app, but have an additional key that is stored on the device that is mixed with the password before being used for (en|de)cryption. If web-based users need to access their encrypted data, your only real option is to make a browser extension. $\endgroup$
    – Tim McLean
    Mar 2, 2015 at 18:10
  • $\begingroup$ Splitting the output of scrypt into a login key and encryption key is probably OK. Note that this may not be the case for other KDFs. PBKDF2, for example, takes time proportional to the number of output blocks. Doubling the output length forces you to halve the iteration count, which reduces the effective security level. $\endgroup$
    – Tim McLean
    Mar 2, 2015 at 18:22

Your Answer

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

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