ARGON2 key derivation with C#Argon2 was designed Alex Biryukov, Daniel Dinu, and Dmitry Khovratovich, and is a key derivation function (KDF), where were we can create hashed values of passwords, or create encryption keys based on a password. It was a winner of the Password Hashing Competition in July 2015, and is robust against GPU and side channel attacks [paper]. It was integrated into OpenSSL 3.2. Argon2d uses a data-dependent approach to memory access, and Argon2i uses a data-independent approach. Argon2id is a hybrid construction. In this case we will use C# to implement the code, and with the Konscious Argon integration. |
Outline
Did you know that every eight character password (in lowercase) is cracked by brute force within less than 10 seconds? And if you use a 9 character password it takes less than 10 minutes to crack them (using lowercase letters)? Adding a number to the end, or making the first letter uppercase makes very little difference in the cracking challenge [here]
Here is me using an 8 GPU instance in the Amazon Cloud:
Thus we need to give up on our traditional ways of generating encryption keys or in creating hashed versions of passwords, as our passwords are often guessable. For MD5 we can now get billions or even trillions or hashes per second, where even 9 or 10 character passwords can be cracked for a reasonable financial cost. This includes salting of the password, as the salt is contained with the hashed password, and can be easily cracked with brute force (or dictionaries).
The alternative is to use a hashing method which has a cost in memory, and for CPU processing. We also want to create a method which makes it difficult to apply parallel threads (and thus run it on GPUs). So step forward Argon2, which was designed Alex Biryukov, Daniel Dinu, and Dmitry Khovratovich as a key derivation function. It was a winner of the Password Hashing Competition in July 2015, and is robust against GPU attacks.
It is resistant to GPU attacks, and also has a memory cost. The costs include: execution time (CPU cost); memory required (memory cost); and degree of parallelism.
The parameters include:
- Password (P): Defines the password bytes to be hashed
- Salt (S): Defines the bytes to be used for salting.
- Parallelism (p): Defines the number of thread that are required for the parallelism.
- TagLength (T): Define the number of bytes to return for the hash.
- MemorySizeKB (m): Amount of memory (in KB) to use.
A sample run is:
Message: abc Hash: $argon2i$v=19$m=8,t=1,p=1$x/3yHil0MIE$RDL1Jw Verified: True
A sample benchmark using n (for time cost), p (for parallelism) is:
We can see that for an n value of 128, that the time to compute a hash value is 0.105 seconds (which would give a hashing function of just 10 hashes per second), but for 8,192 it is 5.78 seconds (which is probably too long for a user login). We must thus select a cost value which gives a reasonable time to compute, but is also robust against attacks. The message (m) is defined in log2(m), and where, in this case, m is \(2^8\) bytes [full test].
Coding
First we create a folder named "argon2" and then in this folder add the .NET framework with:
dotnet new console --framework net8.0
and then add the Argon2 integration:
dotnet add package Konscious.Security.Cryptography.Argon2 --version 1.3.0
The coding is:
namespace Argon2 { using System.Security.Cryptography; using System.Text; using Konscious.Security.Cryptography; class Program { static void Main(string[] args) { var plainTextPassword = "Hello"; var salt = "NaCl1234"; var iterations=1; var memcost=8192; var par=1; var digest=24; if (args.Length >0) plainTextPassword=args[0]; if (args.Length >1) salt=args[1]; if (args.Length >2) iterations=Convert.ToInt32(args[2]); if (args.Length >3) memcost=Convert.ToInt32(args[3]); if (args.Length >4) digest=Convert.ToInt32(args[4]); if (args.Length >5) par=Convert.ToInt32(args[5]); var bytes = Encoding.UTF8.GetBytes(plainTextPassword); var s = Encoding.UTF8.GetBytes(salt); try { var argon2id = new Argon2id(bytes); argon2id.Salt = s; argon2id.DegreeOfParallelism = par; argon2id.Iterations = iterations; argon2id.MemorySize = memcost; var res =argon2id.GetBytes(digest); Console.WriteLine("\nArgon2id (hex):\t\t{0}",Convert.ToHexString(res)); var argon2i = new Argon2i(bytes); argon2i.Salt = s; argon2i.DegreeOfParallelism = par; argon2i.Iterations = iterations; argon2i.MemorySize = memcost; res =argon2i.GetBytes(digest); Console.WriteLine("\nArgon2i (hex):\t\t{0}",Convert.ToHexString(res)); var argon2d = new Argon2d(bytes); argon2d.Salt = s; argon2d.DegreeOfParallelism = par; argon2d.Iterations = iterations; argon2d.MemorySize = memcost; res =argon2d.GetBytes(digest); Console.WriteLine("\nArgon2d (hex):\t\t{0}",Convert.ToHexString(res)); Console.WriteLine("\nPassword:\t\t{0}",plainTextPassword); Console.WriteLine("Salt:\t\t\t{0}",salt); Console.WriteLine("Parallelism:\t\t{0}",par); Console.WriteLine("Iterations:\t\t{0}",iterations); Console.WriteLine("Mem Cost:\t\t{0}",memcost); Console.WriteLine("Digest:\t\t\t{0}",digest); } catch (Exception e) { Console.WriteLine("Error: {0}",e.Message); } } } }
For example:
Argon2id (hex): 583AA9A02860F83B08B4F91D2D81EF70D54E90EA2F1F831D Argon2i (hex): C9E0669AE4F1C7792AC4086FA85044D8C13C9028EC8E9C71 Argon2d (hex): 1AB44F40F4AA21ACF134421157E285AC6589ABD32F346323 Password: Hello Salt: NaCl1234 Parallelism: 1 Iterations: 1 Mem Cost: 8192 Digest: 24