In the past couple of blog posts, I’ve been discussing various password hashing algorithms and how Argon2 (specifically Argon2id) is the algorithm that leading cryptographers recommend. In this post, I want to discuss how to consume the Argon2id algorithm from a C# application using .NET Core.
You have two libraries to choose from if you want Argon2 in the .NET world: Konscious.Security.Cryptography and Libsodium.
This is the third part in a series of blog posts concerning storing a password securely. For background information, see the following posts:
Using Konscious.Security.Cryptography for Argon2
If you want to use Argon2 in a .NET application, the Argon2 documentation recommends the Konscious.Security.Cryptography library.
This library is incredibly simple to consume from a .NET application. Further, it offers all of the flexibility that we need in order to tune Argon2 to our environment. The biggest downside with this library is that it isn’t widely used. As of the time of this post, the project has a mere 50 stars on GitHub and is used by only 29 projects (one of them being my own). Still, it appears to be actively maintained and it works in .NET Core.
So let’s get started. First thing’s first. Add a NuGet package reference to your project. Simply search for “Konscious.Security.Cryptography” and it should be the first one to pop up.
Create a function that will return a random salt. We will use the RNGCryptoServiceProvider to generate a cryptographically secure random number.
private byte[] CreateSalt() { var buffer = new byte[16]; var rng = new RNGCryptoServiceProvider(); rng.GetBytes(buffer); return buffer; }
Create a function to create a password hash taking a password and salt as parameters. In this function, instantiate a new Argon2id object and initialize it with the parameters discussed in my previous post [citation needed]. I am using the sensitive values from Libsodium’s implementation here, with degrees of parallelism with twice the number of CPU cores that I have.
private byte[] HashPassword(string password, byte[] salt) { var argon2 = new Argon2id(Encoding.UTF8.GetBytes(password)); argon2.Salt = salt; argon2.DegreeOfParallelism = 8; // four cores argon2.Iterations = 4; argon2.MemorySize = 1024 * 1024; // 1 GB return argon2.GetBytes(16); }
In order to verify the password hash, we simply need to recalculate the hash given the same inputs. Next, we compare the old hash with the newly calculated hash. This is pretty easy using LINQ:
private bool VerifyHash(string password, byte[] salt, byte[] hash) { var newHash = HashPassword(password, salt); return hash.SequenceEqual(newHash); }
Finally, we can put it all together:
public void Run() { var password = "Hello World!"; var stopwatch = Stopwatch.StartNew(); Console.WriteLine($"Creating hash for password '{ password }'."); var salt = CreateSalt(); Console.WriteLine($"Using salt '{ Convert.ToBase64String(salt) }'."); var hash = HashPassword(password, salt); Console.WriteLine($"Hash is '{ Convert.ToBase64String(hash) }'."); stopwatch.Stop(); Console.WriteLine($"Process took { stopwatch.ElapsedMilliseconds / 1024.0 } s"); stopwatch = Stopwatch.StartNew(); Console.WriteLine($"Verifying hash..."); var success = VerifyHash(password, salt, hash); Console.WriteLine(success ? "Success!" : "Failure!"); stopwatch.Stop(); Console.WriteLine($"Process took { stopwatch.ElapsedMilliseconds / 1024.0 } s"); }
Of course, you never want to log a password or its hash. This is simply for demonstration purposes…
Creating hash for password 'Hello World!'. Using salt 'Wn6hLboKBwVII10VDspD+Q=='. Hash is 'C7ti2i+6lX9g+6oNhs3n5Q=='. Process took 4.4951171875 s Verifying hash... Success! Process took 3.7197265625 s
Using Libsodium for Argon2
NaCl (pronounced “salt”) is one of the most highly respected cryptographic libraries available today. Perhaps the most well-respected cross-platform supported fork of NaCl is Libsodium. A compiled library of Libsodium is available for practically any platform.
Libsodium provides numerous APIs to make working with cryptography easy, thereby preventing a developer from proverbially shooting himself/herself in the foot. However, these are still low-level APIs designed to be cross-platform. So, consuming it comes at a bit of a cost. There are .NET-specific wrappers for Libsodium, but they are either outdated or haven’t garnered enough attention to be fully trusted. And, some of the ones that look to be promising unnecessarily remove critical parameters and make blanket assumptions about others.
Because of this, consuming Libsodium from .NET will probably result in a bit more technical debt than you want. It’s got a great following, though, with close to 7,000 stars at the time of this writing.
First, add a NuGet package reference to libsodium. Don’t be tempted to add a reference to “libsodium-net” or “libsodium-netcore.” Neither of those packages are supported anymore.
Next, we need to add a new class to contain some P/Invoke functions. I’ll borrow from some of the semantics I’ve seen shared among .NET implementations of Libsodium. Note that this isn’t a full-fledged implementation. But, this should be enough to get you going:
public class SodiumLibrary { private const string Name = "libsodium"; public const int crypto_pwhash_argon2id_ALG_ARGON2ID13 = 2; public const long crypto_pwhash_argon2id_OPSLIMIT_SENSITIVE = 4; public const int crypto_pwhash_argon2id_MEMLIMIT_SENSITIVE = 1073741824; static SodiumLibrary() { sodium_init(); } [DllImport(Name, CallingConvention = CallingConvention.Cdecl)] internal static extern void sodium_init(); [DllImport(Name, CallingConvention = CallingConvention.Cdecl)] internal static extern void randombytes_buf(byte[] buffer, int size); [DllImport(Name, CallingConvention = CallingConvention.Cdecl)] internal static extern int crypto_pwhash(byte[] buffer, long bufferLen, byte[] password, long passwordLen, byte[] salt, long opsLimit, int memLimit, int alg); }
Now, create a function that will return a random salt. This will simply wrap the call to Libsodium’s randombytes_buf() function:
private byte[] CreateSalt() { var buffer = new byte[16]; SodiumLibrary.randombytes_buf(buffer, buffer.Length); return buffer; }
Next, create a function to create a password hash. This is a wrapper for the call to Libsodium’s crypto_pwhash() function. In this example, I’m using Libsodium’s sensitive values for iterations and memory usage. Note that Libsodium doesn’t allow you to specify a degree of parallelism here. Libsodium uses a single degree of parallelism. I’m not quite sure why…
private byte[] HashPassword(string password, byte[] salt) { var hash = new byte[16]; var result = SodiumLibrary.crypto_pwhash( hash, hash.Length, Encoding.UTF8.GetBytes(password), password.Length, salt, SodiumLibrary.crypto_pwhash_argon2id_OPSLIMIT_SENSITIVE, SodiumLibrary.crypto_pwhash_argon2id_MEMLIMIT_SENSITIVE, SodiumLibrary.crypto_pwhash_argon2id_ALG_ARGON2ID13 ); if (result != 0) throw new Exception("An unexpected error has occurred."); return hash; }
You can use the same code above to write the results to the console window. Here are my results:
Creating hash for password 'Hello World!'. Using salt 'EQRK407Y4+4XWKxkERFmYw=='. Hash is 't7NZI/QpYYJkWQtObOLNRg=='. Process took 2.5537109375 s Verifying hash... Success! Process took 2.525390625 s
Conclusion
I recommend storing the iterations, memory usage, and degree of parallelism used alongside the salt and hash. That way, if you ever need to modify these parameters (which you will), you’ll be able to migrate your users to more secure values as they log into the system.
Both options above are fairly simple to implement with Konscious.Security.Cryptography requiring slightly less code and more flexibility. Libsodium requires a bit more code and isn’t as flexible, but it comes with the support of quite a large community. I personally feel that whatever library you choose, you’ll be off to a great start at protecting your passwords in the event of a data breach!
I am not able to use the lipsodium dll. it is gining me following error.
unable to load lipsodium.dll. can not be found
Libsodium documentation recommends to use sodium_mlock() & sodium_munlock(). Have you omitted it purposely or some other reason? Can you guide how to use them? Thanks in advance
Excellent article on using Argon2. Thanks for the writeup.
Libsodium documentation recommends to use sodium_mlock() & sodium_munlock(). Have you omitted it purposely or some other reason? Can you please guide how to use them? Thanks in advance
Thank you for this great article. I used Konscious an everythings works fine. I want to generate a hash that looks like that one in your JAVA article Argon2-jvm (https://www.twelve21.io/how-to-use-argon2-for-password-hashing-in-java/). The Hash should look like this “$argon2id$v=19$m=1048576,t=4,p=8$9m35jyZUsdtJ8ON2K6xAjg$dMIdvc17guXazUEo2/DWyih10h7AoK3m+KuHan40yI8”. I add the parameters manualy but got a different length. I’am not able to generate a hash that is equal (equal length) to generated ones with online Generators. For example (your Parameters, random Salt) my Hash looks like this “$argon2i$v=19$m=1048576,t=4,p=8$q7iuMDp4KLhjI92gth2P5g==”. It’s too short! The online generated hashs works fine (used for administration in typo3) the konscious generated don’t. What am I missing? Please help.
i am getting this error
System.OutOfMemoryException’ was thrown.
Most likely the amount of memory being utilized for Argon2 exceeds that what is available to your process. In this case, I would reduce the value of either the MemorySize property (if using Konscious), or reduce the value to the memlimit attribute in the call to crypto_pwhash() (if using Libsodium).
Thank you very much for the article, Bryan. It is an excellent guide.
I used Konscious, but I am using .NET 4.5 in my project. For that reason I have switched to using Libsodium, but I have problems using it.
I have installed Libsodium through NuGet, but the Sodium library does not appear in the object browser. I have not been able to generate the SodiumLibrary class. Do you have a reference or link to complete it, please?
Regards.
I think libsodium can only be used with older .net versions and cannot be used with .net versions above 3.0 . It shows “CodeTaskFactory” Error on newer versions and it does not allow to build also.