1

I read an article about how to use Argon2id in C# here.

Below is the code they wrote (slightly edited):

using System;
using System.Diagnostics;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using Konscious.Security.Cryptography;   

namespace Playground
{
    class Program
    {
        // No. of CPU Cores x 2.
        private const int DEGREE_OF_PARALLELISM = 16;

        // Recommended minimum value.
        private const int NUMBER_OF_ITERATIONS = 4;

        // 600 MB.
        private const int MEMORY_TO_USE_IN_KB = 600000;

        static void Main(string[] args)
        {
            var password = "SomeSecurePassword";               
            byte[] salt = CreateSalt();
            byte[] hash = HashPassword(password, salt);                

            var otherPassword = "SomeSecurePassword";                                
            var success = VerifyHash(otherPassword, salt, hash);                
            Console.WriteLine(success ? "Passwords match!" : "Passwords do not match.");                
        }

        private static byte[] CreateSalt()
        {
            var buffer = new byte[16];
            var rng = new RNGCryptoServiceProvider();
            rng.GetBytes(buffer);

            return buffer;
        }

        private static byte[] HashPassword(string password, byte[] salt)
        {
            var argon2id = new Argon2id(Encoding.UTF8.GetBytes(password));
            argon2id.Salt = salt;
            argon2id.DegreeOfParallelism = DEGREE_OF_PARALLELISM;
            argon2id.Iterations = NUMBER_OF_ITERATIONS;
            argon2id.MemorySize = MEMORY_TO_USE_IN_KB;

            return argon2id.GetBytes(16);
        }

        private static bool VerifyHash(string password, byte[] salt, byte[] hash)
        {
            var newHash = HashPassword(password, salt);
            return hash.SequenceEqual(newHash);
        }
    }
}

I have the following questions:

  1. On the Konscious.Security.Cryptography README page, instead of argon2id.GetBytes(16), they are using argon2.GetBytes(128) which returns a longer value.

Assuming the configurations are the same, is the 128 approach more secure than the 16 one because it's longer?

  1. From what I understand, the more memory we let Argon2id use, the more secure it will be against customized hardware attacks.

I therefore assume that even if 40 iterations with 70 MB and 4 iterations with 600 MB take roughly the same time, the latter configuration's larger memory cost is justified because it's more secure. Is this correct?

2

According to the Argon2 Specs, specifically section 9 Recommended Parameters, the following procedures should be done to determine which parameters to use:

We recommend the following procedure to select the type and the parameters for practical use of Argon2:

  1. Select the type y. If you do not know the difference between them or you consider side-channel attacks as viable threat, choose Argon2i. Otherwise any choice is fine, including optional types.
  2. Figure out the maximum number h of threads that can be initiated by each call to Argon2.
  3. Figure out the maximum amount m of memory that each call can afford.
  4. Figure out the maximum amount x of time (in seconds) that each call can afford.
  5. Select the salt length. 128 bits is sufficient for all applications, but can be reduced to 64 bits in the case of space constraints.
  6. Select the tag length. 128 bits is sufficient for most applications, including key derivation. If longer keys are needed, select longer tags.
  7. If side-channel attacks is a viable threat, enable the memory wiping option in the library call.
  8. Run the scheme of type y, memory m and h lanes and threads, using different number of passes t. Figure out the maximum t such that the running time does not exceed x. If it exceeds x even for t = 1, reduce m accordingly.
  9. Hash all the passwords with the just determined values m, h, and t.

The "tag" in this case refers to the output of Argon2. So as the document highlights, 128 bits (or 16 bytes), should be enough for most applications. Generating a longer output will not give you more security. You should generate a longer output if your application needs the output to be longer for some other purpose (e.g. generating 256 bits output to use as a key for AES-256).


As for the memory cost compared to the number of iterations, I asked a similar question. The answer to that question, as well as the specs, recommend the maximum amount of memory possible given your environment. This question and its corresponding answers explain why it makes sense to maximize memory usage, as long as you have at least 3 iterations over that memory. In your code, NUMBER_OF_ITERATIONS is set to 4 as a recommended minimum value, which seems safe as a minimum.


As a side note, you should actually benchmark your values. What primarily matters is the amount of time it takes to perform the calculation of one Argon2 tag. The specification added above, as well as the questions I linked to, detail this process well.

| improve this answer | |
  • Many thanks for the detailed response! I'm going to read the links you provided as well. :) – Floating Sunfish Mar 31 at 8:06
  • 1
    Three iterations is the recommended minimum for Argon2i. For Argon2id it's two. For Argon2d it's one. (Prior to version 1.3 there were a lot of recommendations to use many more passes for Argon2i. The concern that drove that was addressed in version 1.3, so three is now sufficient.) – Future Security Apr 1 at 2:44
  • 1
    "What primarily matters is the amount of time it takes to perform the calculation of one Argon2 tag."   Run time (defender-side) doesn't matter as much as how many hashes per second an attacker can perform. So it's run time and how difficult it is to parallelize the hash that matters. That's why the maximize memory step is prioritized. Fortunately users don't need to do more than benchmark on their own hardware if they follow that advice. (Preferably using 100's or 1000's of MB. Maybe more in the future.) – Future Security Apr 1 at 3:04
  • 1
    (It's one of my missions to change the usual "slow hash function" or "maximizing runtime" narrative to a "maximizing cracking costs" narrative. I think people still recommend PBKDF2 for that reason, despite it being much easier to crack. Equal hash computation latency doesn't mean equal security. Running PBKDF2 for 1 second is much weaker than running Argon2 for 1 second. Sadly we've got too many others recommending primitive, easily parallelized password hashing function. Often with a hint of "Argon2 is too new/complicated" and NIH syndrome.) 🙃 – Future Security Apr 1 at 3:19
  • 1
    Any amount of memory will be safer than PBKDF2. When it comes to simple algorithms like PBKDF2, s2k, md5crypt, shacrypt, etc. they're all about equally easy to parallelize and are sequential calculations. In a competition between those algorithms you merely want to maximize the efficiency of the iterated function on the defender's computer.relative to the most optimized (software or hardware) implementation of that algorithm. They all use functions with a huge gap between pure-software speed and hardware-acceleration speed. That gap is small for Argon2's internals, so Argon2 would always win. – Future Security Apr 1 at 10:08

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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