3
$\begingroup$

I'm using PBKDF2 in browser (can't use bcrypt, users are likely to have passwords > 72bytes, or whatever the number was). PBKDF2 is running in a webworker, something like so

const key = PBKDF2_HMAC_SHA512.bytes(passphrase, salt, Math.pow(2,17), 64)

This key is used to encrypt data to be stored in the user's localstorage. Now I wonder, seeing as I'm already using a webworker, why not use multiple? Would I not be able parrallelize it? Could I not do something like(simplified, in reality each key is generated in a separate web worker)

const key1 = PBKDF2_HMAC_SHA512.bytes(passphrase, salt1, Math.pow(2,17), 16)
const key2 = PBKDF2_HMAC_SHA512.bytes(passphrase, salt2, Math.pow(2,17), 16)
const key3 = PBKDF2_HMAC_SHA512.bytes(passphrase, salt3, Math.pow(2,17), 16)
const key4 = PBKDF2_HMAC_SHA512.bytes(passphrase, salt4, Math.pow(2,17), 16)
const key5 = new Uint8Array(64)
key5.set(key1)
key5.set(key2, 16)
key5.set(key3, 32)
key5.set(key4, 48)
const key = SHA_512.bytes(key5)

Would this not be 4x more secure? Or more importantly, could I not halve the rounds per instance of pbkdf2 and still end up with 2x the security? If I'm wrong, could someone point me in the right direction for a faster/more secure parallelizable kdf?

$\endgroup$
7
  • 1
    $\begingroup$ You can use SHA512 to reduce the passwords of the users to under 72 bytes (or whatever the number was) and then just use bcrypt. $\endgroup$
    – Nova
    May 3, 2018 at 11:45
  • $\begingroup$ Would I be able to spread bcrypt across multi threads in the same way as above...? $\endgroup$
    – Irontiga
    May 3, 2018 at 12:56
  • 1
    $\begingroup$ The idea is to concatenate key1..key4 and hash that, which (with proper definition of salt1..salt4 and actual multithreading) would be a relatively sound way to multithread a native PBKDF2 (though not nearly as good against password search as a native Balloon, Argon2, Scrypt, or I guess even Bcrypt). But I can't find anything justifying that the code shown actually does that. I'm inclined to believe that it ignores key1..key3, concatenates key4 and 48 zero bytes, then hash that. This detail is off-topic, but if I'm right, it is illustrative of how things can go bad in crypto implementations. $\endgroup$
    – fgrieu
    May 3, 2018 at 14:56
  • $\begingroup$ Yes, the code concatenates keys 1 through 4. Salts are from crypto.getRandomValues (secure random function, developer.mozilla.org/en-US/docs/Web/API/Crypto/getRandomValues). Why would it ignore keys 1 to 3? $\endgroup$
    – Irontiga
    May 3, 2018 at 15:15
  • 1
    $\begingroup$ Ahh my bad, yes. Updated $\endgroup$
    – Irontiga
    May 4, 2018 at 7:29

2 Answers 2

5
$\begingroup$

Yes, this would improve security. In fact, you do not even need to use individual salts. Simply concatenating the thread number as a counter with the input key would be enough:

Input: k, a salted key to be strengthened with a KDF
Output: h, the final output hash

spawn_thread(h0 := slow_KDF(k || 0))
spawn_thread(h1 := slow_KDF(k || 1))
spawn_thread(h2 := slow_KDF(k || 2))
...
spawn_thread(hn := slow_KDF(k || n))

wait_for_all_threads()

h := fast_hash(h0 || h1 || h2 || ... || hn)

However, this is not particularly efficient. Every time you double the number of threads (assuming each thread is truly independent and not sharing any resources), you add the equivalent of one bit of security. A single extra byte added to the input provides the equivalent increase in difficulty as running this on a system with 256 independent hardware threads. Assuming you will not get more than 8 parallel KDF instances, you will never increase security by more than a mere 3 bits...

Instead, you should be using a KDF that is memory-hard, preventing an attacker from using a GPU or ASIC from parallelizing an attack against PBKDF2. An example would be Argon2, which natively supports parallelism in addition to a configurable amount of memory.

$\endgroup$
6
  • $\begingroup$ Also we usually tend to assume that the attacker has lots of parallelism available anyways, so adding more threads / parallel instances won't hurt them, but may even benefit them if they use GPUs / FPGAs... $\endgroup$
    – SEJPM
    May 5, 2018 at 10:53
  • 1
    $\begingroup$ @SEJPM It wouldn't benefit them because each individual thread is independent. Adding another thread does not slow down an existing thread, assuming no more concurrent threads are run than the number of hardware threads present. $\endgroup$
    – forest
    May 5, 2018 at 11:07
  • $\begingroup$ Thanks! I will see about using other kdfs. As to parallelizing, doubling the thread is = to doubling the iteration count, which could be the difference between 1 second and 0.25 seconds for the user to wait for key generation (assuming 4 threads of course). $\endgroup$
    – Irontiga
    May 6, 2018 at 11:43
  • $\begingroup$ @Irontiga It's more like doubling it from half a millisecond to one second. It's far easier to make something a thousand times slower than it is to get a thousand core processor. $\endgroup$
    – forest
    May 6, 2018 at 12:00
  • $\begingroup$ @forest No one needs a thousand core processor? This could be attacked with only 1 core...the only advantage is that you get better performance and/or security for the user. $\endgroup$
    – Irontiga
    May 7, 2018 at 5:47
2
$\begingroup$

As forest said, the security effect of parallel threads is very low and Argon2 would be a better choice.

It might be confusing, that Argon2 also uses a build-in parallelism, but it uses it in a much more efficient way.

To make a key derivation function memory hard, you generally build a large pseudo-random vector. Accessing the vector makes the use of custom hardware very expensive or even impossible (this is a bit simplified). The problem for all memory hard key derivation functions is, that creating such a large vector takes also a lot of time. If you want to run Scrypt, Catena or Lyra2 with a large amount of memory, you need a lot of time to create the vector. So, even if there is much memory available, you can't use it for the key derivation because of the time.

The clever idea of the Argon team was to use parallelism to create the vector, not parallel instances of the key derivation function. Thanks to this idea, Argon2 can be memory hard in a limited amount of time. I guess that this was a least one reason, why Argon2 was the winner of the password hashing competition.

In short: parallelism to increase the memory filling rate is efficient, but to increase the time amount it is not.

$\endgroup$

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.