1
$\begingroup$

TLDR; Is there a security risk if rather than setting the iteration parameter to 12 (for example), we set it to 3 but call the function 4 times?

I'm asking that for 2 reasons:

Firstly, we use SRP protocol where the deprecated SHA1 is replaced by Argon2id inside our PWA (Progressive Web App, so in JavaScript) but Firefox and Google currently discuss to inform user when an operation take too much CPU or RAM. Their goal is to fight against website that doing bitcoin's mining in the background. I don't want this require user's confirmation each time we use Argon2.

Secondly, as we can't known which CPU/RAM are available for the smartphone in JavaScript, we can't known the ideal number of iterations to ideally doing, so we need to find this with the help of a chronometer, we can call Argon2id as many time as possible until 3 seconds elapsed. Of course, we also keep in memory the number of calls used the first time, to do the exact same number of calls the next time we want to compare.

BTW, the last RFC 8018 of January 2017 still recommend PBKDF2 and this KDF is natively supported in browser (WebCrypto API) and faster than any external library, so may be the solution is to not use Argon2 and to use WebCrypto PBKDF2, with the same method of multiple calls until 3 seconds elapsed?

$\endgroup$
1
  • $\begingroup$ This is a perfect example of why good implementations of "long"-running algorithms like Argon2id should provide a way for the caller to pause and resume the KDF (either by literally having APIs to do one "iteration" at a time on some working state, or by taking a callback from the user which the KDF regularly calls to check if it needs to pause). $\endgroup$
    – mtraceur
    Jun 5, 2022 at 15:05

3 Answers 3

1
$\begingroup$

I'm adding my solution here for the record.

Rather than calling multiple times the Argon2d function when the user sign in, we can do something smarter, with a research of the best settings when the user sign up.

We can inform him we will take few seconds to adjust the security while 5 ~ 10 seconds. So if the browser block the process and warn the user, he's already informed and can let continue the process.

Technically, we trying different settings from the lower values (1MB / 1 iteration) until the delay measured require at least 1 second. Then, we could save the settings to use for this user's device. When the user sign in, we simply use the associated settings, and this will not take more than 1 ~ 2 seconds. So in theory this shouldn't be blocked by the browser, and we increase security in following the device's capabilities.

Some real tests:

  • Apple iPhone 4S (512MB - 1Ghz): ~1.7s ==> m=1MB,t=1
  • Apple iPhone 5 (1GB - 1.3Ghz dual-core): ~1.2s ==> m=4MB,t=1
  • Samsung S8 (4 GB, 2.3GHz / 1.7GHz quad-core each): ~1.4s ==> m=24MB,t=2
  • NUC i3 (8GB - 1.7Ghz quad-core): ~1.4s ==> m=24MB,t=2

Any feedback are welcome.

$\endgroup$
4
  • $\begingroup$ Just so you know, as a user, I would find it frustrating to have my security "configured" on a fast high-end device, only to log in later on a very slow low-end device. For example, Argon2id 3 iterations 1 lane 16 MiB in the browser takes ~250ms on a Pixel 5 but ~1400ms on a Samsung Galaxy 5s. Assuming that scales linearly: if you gave me parameters that look ~1-2 seconds on a Pixel 5, and one day I log in with a Samsung Galaxy S, I'd be waiting for 5-12 seconds to log in. $\endgroup$
    – mtraceur
    Jun 5, 2022 at 15:13
  • $\begingroup$ As a technical user who has thought about this exact problem before, I would probably recognize what was happening, but it would still be frustrating. I realize this is a fairly uncommon situation, and some users might prefer the increased security even if it meant that logging in from grandma's old computer is unpleasant. But it might be good to have a recalibration, like maybe if you detect that a run during login is significantly slower than expected, you prompt the user "if you regularly use this device, do you want to [recalibrate / slightly reduce] security to make logins faster?" $\endgroup$
    – mtraceur
    Jun 5, 2022 at 16:07
  • $\begingroup$ The holy grail solution is "oblivious" key derivation or key stretching - like how we have oblivious salting by using an "oblivious pseudorandom function", it would be great if someone came up with a way for the client to "blind" their password, send it to the server for the KDF, and then un-blind the result from the server to get the key. I haven't heard of one just yet, and the OPAQUE protocol which was recently standardized is still doing key stretching on the client, which makes me think efficient-enough-to-be-practical oblivious key stretching hasn't been invented yet. $\endgroup$
    – mtraceur
    Jun 5, 2022 at 16:11
  • $\begingroup$ So I asked about offloading the KDF/KSF to the server without the server actually learning the result, and it turns out there is Makwa, which isn't memory hard but does provide this offloading functionality (they call it "delegation", though I don't like calling it delegation because that is more ambiguous with many other very valuable uses of the word "delegation") - idea being that offloading alleviates the need for memory hardness (which I'm not fully convinced of). $\endgroup$
    – mtraceur
    Jun 6, 2022 at 19:04
0
$\begingroup$

I assume you mean the execution time parameter with "iterations". Argon2 was designed to be as memory hard per execution time as possible (along with other critera). If you call Argon2 several times with lower parameters, you are interfering with that goal. Although there are no insecure parameter values, lower values makes an attack easier. That is, the duration of time for a particular hardware of an attack is lower - or the cost of customized hardware to crack the password in a feasible time.

PBKDF2 is not an alternative. The resistance to custom hardware attacks is close to zero, and if you increase the number of iterations to an appropriate number, the same browser problem occurs. If Firefox and Chrome really decide to inform users about time-cosuming and memory-consuming operations to protect mining, then that would be an unsolvable problem for your (and any equivalent) application. Mining is very similar to password hashing (Litecoin and other mining is also memory hard). A good password hashing would therefore always be affected by this measure.

$\endgroup$
4
  • $\begingroup$ Thank you for your answer. So it's possible to call multiple times Argon2, this decrease a little bit the quality of the security, but it's surely better than a blocking operation intercepted by the browser and better than PBKDF2. $\endgroup$
    – lakano
    Nov 9, 2017 at 9:34
  • $\begingroup$ Yes, it's possible, but I'm not sure this will solve your problem. Even with lower parameters Argon2 is maybe as time-consuming as bitcoin mining. $\endgroup$
    – BeloumiX
    Nov 9, 2017 at 10:06
  • $\begingroup$ Yep, all depends of how they will measure it. I hope to not be stopped if we do small sleep time between each calls. $\endgroup$
    – lakano
    Nov 9, 2017 at 10:15
  • $\begingroup$ For information, after multiple tests, I've added my solution $\endgroup$
    – lakano
    Nov 9, 2017 at 17:16
0
$\begingroup$

One other solution is to try to first do a quick microbenchmark, and then decide if the user is logging in with approximately the capabilities you've seen before. If no, besides comparing against existing saved results, generate new independent results that can be used next time for devices closer to this performance.

Basically, you can have multiple tuples of

  • Argon2id parameters
  • salt, and
  • derived public key or whatever you're using for comparison,

for each user. Basic one-to-many relationship.

When a user starts a login (or maybe even preemptively, if you have a good heuristic for when to do it, or don't mind hitting the user with a microbenchmark even if they weren't going to log in) you can start that microbenchmark in the background.

When that microbenchmark is done, pick Argon2id parameters that are a good fit for results in that ballpark, and then:

  • If you already have an entry for those parameters, run Argon2id with those parameters and that salt, and compare with those keys.

  • If you don't already have an entry for those parameters,

    1. do the login with the closest parameters you do have an entry for, and then
    2. generate a new salt (if each parameterization of Argon2id for a user gets its own salt, then even if an attacker is able to get the existing salts, the attacker still can't benefit from pre-computing rainbow tables with weaker parameters and then causing their target user to run on a system that benchmarks to those weaker settings), generate new keys, and save that new entry for later. (This step can be done in the background, you don't have to block the UI or get in the user's way.)

Expire the entries which aren't used for a while, maybe with a bias for expiring weaker or uncommonly used entries sooner. (This way, if a user logs in once from some old or weak device and then doesn't for a while, you're not keeping their weaker less crack-resistant results around indefinitely, reducing the time window during which a database leak or similar could expose those weaker entries.)

Of course, alternatively, you could just keep only the weakest Argon2id parameters and derived public key seen so far... the one argument for multiple entries being a bad idea is that there are multiple possible results the attacker could try to brute force, and maybe one day it turns out that Argon2id is easier to crack if the salt has very specific bits or if you have multiple results with different iterarions (in which case multiple entries with individual salts means more chances to have one with such a bad salt). But I would still keep the Argon2id settings from those entries, with at least a timestamp of the most recent time they logged in from a device that would get those settings, so that you can still expire the weakest entry (and delete it on next login after upgrading to a stronger one) - otherwise your system ends up a ratchet that only ever downgrades settings, which is the exact opposite of the goal here.

With all this in place, the work factor automatically tunes itself for each user's current systems. The only weakness is that if an attacker can raise the user's CPU and memory usage, or get them to use a device with lower specs, that can become a downgrade attack instead of just making login slow. But of course you'd still impose a minimum acceptable floor on the Argon2id settings, right? The strength is that as your user upgrades their device, their work factor will automatically increase after a slight lag time.

Is this scheme actually good? Ehh, decide for yourself. Personally I like it, and I'd rather use something like that than something that got stuck on whatever work factor was appropriate for the one device I registered from, but I'd want good cryptographers and security experts to weigh in and look at it critically first.

$\endgroup$

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.