I love that way that Argon2id lets you tune the cost. For non-interactive things, such as provisioning a new system or unlocking a key vault, you can make it use 30 seconds of CPU and 1GiB of RAM, which will be a minor inconvenience to a user for an infrequent operation but a huge headache for an attacker (with 1 GiB of state, GPU or FPGA acceleration is unlikely to help, so they’re stuck on CPUs and that means each attempt is 30s of CPU time). You can then use that with a KDF to provision a key into a TPM or similar.
As an implementation note, it’s worth adding that the T parameter (Argon2idTime in the linked article) is typically defined as the number of passes over memory, not the absolute time to spend on the computation. So most users will likely adjust the three parameters—memory, number of passes, parallelism—together [*], such that each attempt overall requires e.g. 30 seconds on the attacker’s computing resources.
[*] Of these three parameters, you should still generally first choose a high memory value to restrict attacker GPU use, like the parent comment said.
Yup, that’s a great point. It’s also worth noting that this changes the outcome of the hash and so it isn’t something that you can change arbitrarily. That said, I doubt there’s more than a factor of ten difference in single-threaded performance for memory-bound workloads between consumer hardware and a plausible attacker and taking 3s of CPU time on the attacker’s machine is still expensive. Cheap VMs start at around $3.50/month/core, that works out at about 0.0004 cents per try. If you have 8 random letters, it will cost a shade under half a million to have a 50% chance of guessing at that price. With 8 upper and lowercase random letters, it’s around $100m. Even if the price halves every year, that’s probably not too bad for most things.
it wasn’t useful because the target service would always mix the salt back in, causing a hash mismatch and failed authentication. So now we were safe against database leaks again.
That’s not really the benefit of the salt. Since your target service would already be hashing the value it if you just sent the hash it would already fail. The benefit of the salt is to break precomputed tables since the attacker would need to create hashes for every password + every salt combination.
conceivably plug password values plus the salt into trillions of operations until finding a hash collision
In theory it could be a collision but I think the issue is much more about finding the original password. A collision sort of implies that you’re finding a distinct values, not the original value, that produces the same hash as the original value.
Hashing PBKDF2 at 600k iterations had been taking ~0.7s (depending on input password) which was nearing the point of unacceptable. The Argon2id configuration is about ten times faster at ~0.06s for the same input.
That’s pretty cool. It’s interesting that “hashes alone are too fast” isn’t quite right anymore as it’s really more nuanced - it’s more like “hashes are too fast and these older PBKDFs are too easy to optimize on specialized hardware”. So you actually want something that’s very fast on your hardware and not much faster on specialized hardware, not something that’s just “slow”. Creating memory hard PBKDFs is definitely a massive win.
One thing about upgrading is that you can only upgrade when the user logs in. That’s less than ideal. One thing you can do though is encrypt your hashes - this may elicit a gasp since most of the internet will say “don’t encrypt, hash!”. But it’s about hashing first and then encrypting.
Being out of band it is less likely to be compromised in the case of a vulnerability such as SQL injection where the attacker just SELECT *’s from your db. And you can always change the encryption algorithm, the parameters, or the secret by decrypting and re-encrypting.
Even with the pepper an attacker is going to have to perform the additional work of performing AES operations. Not that they’re slow, but the nice thing is that they’re really fast on commodity hardware so you aren’t paying a price.
Anyway, good article and major kudos for seeing “that iteration count seems low, let me make this safer”. If every developer thought that way the world would be a much safer place.
If you need to migrate right away, there’s a trick where you take the output of the opd pbkdf and pass that to the new pbkdf as input.
I love that way that Argon2id lets you tune the cost. For non-interactive things, such as provisioning a new system or unlocking a key vault, you can make it use 30 seconds of CPU and 1GiB of RAM, which will be a minor inconvenience to a user for an infrequent operation but a huge headache for an attacker (with 1 GiB of state, GPU or FPGA acceleration is unlikely to help, so they’re stuck on CPUs and that means each attempt is 30s of CPU time). You can then use that with a KDF to provision a key into a TPM or similar.
As an implementation note, it’s worth adding that the T parameter (
Argon2idTime
in the linked article) is typically defined as the number of passes over memory, not the absolute time to spend on the computation. So most users will likely adjust the three parameters—memory, number of passes, parallelism—together [*], such that each attempt overall requires e.g. 30 seconds on the attacker’s computing resources.[*] Of these three parameters, you should still generally first choose a high memory value to restrict attacker GPU use, like the parent comment said.
Yup, that’s a great point. It’s also worth noting that this changes the outcome of the hash and so it isn’t something that you can change arbitrarily. That said, I doubt there’s more than a factor of ten difference in single-threaded performance for memory-bound workloads between consumer hardware and a plausible attacker and taking 3s of CPU time on the attacker’s machine is still expensive. Cheap VMs start at around $3.50/month/core, that works out at about 0.0004 cents per try. If you have 8 random letters, it will cost a shade under half a million to have a 50% chance of guessing at that price. With 8 upper and lowercase random letters, it’s around $100m. Even if the price halves every year, that’s probably not too bad for most things.
Good article, just want to note that:
That’s not really the benefit of the salt. Since your target service would already be hashing the value it if you just sent the hash it would already fail. The benefit of the salt is to break precomputed tables since the attacker would need to create hashes for every password + every salt combination.
In theory it could be a collision but I think the issue is much more about finding the original password. A collision sort of implies that you’re finding a distinct values, not the original value, that produces the same hash as the original value.
That’s pretty cool. It’s interesting that “hashes alone are too fast” isn’t quite right anymore as it’s really more nuanced - it’s more like “hashes are too fast and these older PBKDFs are too easy to optimize on specialized hardware”. So you actually want something that’s very fast on your hardware and not much faster on specialized hardware, not something that’s just “slow”. Creating memory hard PBKDFs is definitely a massive win.
One thing about upgrading is that you can only upgrade when the user logs in. That’s less than ideal. One thing you can do though is encrypt your hashes - this may elicit a gasp since most of the internet will say “don’t encrypt, hash!”. But it’s about hashing first and then encrypting.
https://dropbox.tech/security/how-dropbox-securely-stores-your-passwords
Being out of band it is less likely to be compromised in the case of a vulnerability such as SQL injection where the attacker just SELECT *’s from your db. And you can always change the encryption algorithm, the parameters, or the secret by decrypting and re-encrypting.
Even with the pepper an attacker is going to have to perform the additional work of performing AES operations. Not that they’re slow, but the nice thing is that they’re really fast on commodity hardware so you aren’t paying a price.
Anyway, good article and major kudos for seeing “that iteration count seems low, let me make this safer”. If every developer thought that way the world would be a much safer place.