3
$\begingroup$

I would like to know if splitting a key generated by PBKDF2 to derive two keys is a safe practice.

Concretely, in my system, I need to derive two keys. One for symmetric cipher used in the client side, and one for authenticating the user by sending to the server. The first key is never sent to the server so that only the client knows how to decrypt ciphertexts.

  1. User enters email and password;
  2. Derive k <- pbkdf2(password: password, salt: email, alg: 'sha256', iteration: 10000, dkLen: 512);
  3. Split k into (k0, k1) = (k[0:255], k[256:512]). Use k0 as client side key and send k1 to the server.

My idea is that there is no risk in splitting the key in such manner because the key is a pseudo-random string. Any ideas?

$\endgroup$
7
  • $\begingroup$ Does second paragraph need rewriting? Why not use two different salts to generate two keys? $\endgroup$
    – kelalaka
    Commented Jan 30, 2019 at 20:13
  • $\begingroup$ Note that you should not store k1 at the server without an additional hash. Otherwise an adversary may find a copy of the DB and directly send k1 to the service to log in; the adversary would not require the password anymore for your service, even if the password is still secure. $\endgroup$
    – Maarten Bodewes
    Commented Jan 30, 2019 at 21:07
  • 1
    $\begingroup$ @kelalaka It's better to run one PB-KDF once than two PB-KDFs that are each half as strong. Use a key-based KDF to derive your keys from one master key. Unfortunately PBKDF2 does things the wrong way, making the defender do at least twice as much work if dkLen > hLen while not doubling the password cracker's work. $\endgroup$ Commented Jan 30, 2019 at 21:08
  • 2
    $\begingroup$ Yep, two calculations with a different salt would not solve this; you would still have to perform two calculations to calculate k0 and k1, while the adversary only needs to perform one of them to guess the password input. $\endgroup$
    – Maarten Bodewes
    Commented Jan 30, 2019 at 21:12
  • $\begingroup$ @FutureSecurity you are right, The question has changed that I understood better, now. Splitting and sending half is not a good idea. Using two passwords may be better. $\endgroup$
    – kelalaka
    Commented Jan 30, 2019 at 21:17

1 Answer 1

5
$\begingroup$

It's secure as the bits are unrelated. It is not secure because it removes a bit of security by giving advantage to an attacker.

PBKDF2 has indeed been defined to generate a dynamic amount of key material. The bits of the output are independent of each other, and having any part of the output doesn't give you any information about the rest of the output values. So $k_0$ and $k_1$ are unrelated because PBKDF2 is indeed a pseudo random generator. Great.

However, PBKDF2 has also a design problem: if you require more than the output of the hash function (in this case SHA-256) then it requires you to perform all the iterations again to calculate the next block of output. This is problematic as an adversary may not need to perform those calculations themselves.

For instance, if the attacker just needs to compare against a value encrypted with $k_0$ then the adversary doesn't need to calculate $k_1$: just calculating and validating $k_0$ is enough to guess the password. You, on the other hand, need $k_1$ so you will have to perform all that work without gaining any advantage from it. As the adversary only needs half the work, obviously you lose one bit of secure. And, as your keys are both the exact output size of the hash function, you've inadvertently made it easy for the adversary.

There are a few ways around this. First of all, you can use a more modern function such as Argon2 (check for secure Argon2 configurations first). Secondly, it is possible to use the output of PBKDF2 and derive two keys from it using a key based key derivation function or KBKDF such as HKDF, e.g. k0 = HKDF(k, "Enc") and k1 = HKDF(k, "Auth").

Finally, you can "hack" PBKDF2 by using SHA-512 and have it deliver 512 bits, which you can split. That's solution obviously doesn't scale well, but it is useful if the other options are not available to you, and it is very simple to achieve; you just have to change the hash function after all.

$\endgroup$
7
  • $\begingroup$ Note that "one bit of security" is not much if you're talking about e.g. an AES-256 key. However, the work factor of PBKDF2 doesn't provide nowhere near that kind of security, especially with a relatively low iteration count of 10000. Of course, given the last two solutions, you can double the iteration count without increasing the CPU time (much). $\endgroup$
    – Maarten Bodewes
    Commented Jan 30, 2019 at 21:21
  • $\begingroup$ What about first generate the encryption key k0 with the password then using the generated key as password to arrive k1? $\endgroup$
    – kelalaka
    Commented Jan 30, 2019 at 21:22
  • $\begingroup$ If you also use k0 to encrypt something (which seems to be the use case) then you can still test the password against k0 without involving k1, and you would still be doing twice the work. k1 would now also depend on k0. Actually, that may be worse than the double salt solution. $\endgroup$
    – Maarten Bodewes
    Commented Jan 30, 2019 at 21:25
  • $\begingroup$ But the k0 only used in client side. Assuming the keys are correctly removed the system after enc/dec who will know the k0? $\endgroup$
    – kelalaka
    Commented Jan 30, 2019 at 21:28
  • $\begingroup$ Thanks for your answer. @kelalaka, interesting, I think your suggestion has an advantage in that the client does not have to send a secret key k0 to the server. In other words, it does not have to trust the server. Although k1 depends on k0, since k0 is a pseudo-random string, attacker cannot efficiently reverse k1 to obtain k0. What do you think? $\endgroup$
    – mc9
    Commented Jan 30, 2019 at 21:34

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.