TL;DR use Microsoft.AspNetCore.Cryptography.KeyDerivation, implementing PBKDF2 with SHA-512.
The good idea to get started with password hashing is to look at what OWASP guidelines say. The list of recommended algorithms includes Argon2, PBKDF2, scrypt, and bcrypt. All these algorithms can be tuned to adjust the time it takes to hash a password, and, correspondingly, the time to crack it via brute-force. All these algorithms utilize salt to protect from rainbow tables attacks.
Neither of these algorithms is terribly weak, but there are some differences:
- bcrypt has been around for almost 20 years, has been widely used and
has withstood the test of time. It is pretty resistant to GPU
attacks, but not to FPGA
- Argon2 is the newest addition, being a winner of 2015 Password hashing competition. It has better protection against GPU and FPGA attacks, but is a bit too recent to my liking
- I don't know much about scrypt. It has been designed to thwart GPU- and FPGA- accelerated attacks, but I've heard it turned out to be not as strong as originally claimed
- PBKDF2 is a family of algorithms parametrized by the different hash
functions. It does not offer a specific protection against GPU or ASIC attacks, especially if a weaker hash function like SHA-1 is used, but it is, however, FIPS-certified if it matters to you, and still acceptable if the number of iterations is large enough.
Based on algorithms alone, I would probably go with bcrypt, PBKDF2 being the least favorable.
However, it's not the full story, because even the best algorithm can be made insecure by a bad implementation. Let's look at what is available for .NET platform:
- Bcrypt is available via bcrypt.net. They say the implementation is based on Java jBCrypt. Currently there are 6 contributors and 8 issues (all closed) on github. Overall, it looks good, however, I don't know if anyone has made an audit of the code, and it's hard to tell whether an updated version will be available soon enough if a vulnerability is found. I've heard Stack Overflow moved away from using bcrypt because of such reasons
- Probably the best way to use Argon2 is through bindings to the
well-known libsodium library, e.g.
https://github.com/adamcaudill/libsodium-net. The idea is that
the most of the crypto is implemented via libsodium, which has considerable
support, and the 'untested' parts are pretty limited. However, in
cryptography details mean a lot, so combined with Argon2 being
relatively recent, I'd treat it as an experimental option
- For a long time, .NET had a built-in an implementation of PBKDF2 via
Rfc2898DeriveBytes class. However, the implementation can only use SHA-1 hash function, which is deemed too fast to be secure nowadays
- Finally, the most recent solution is
Microsoft.AspNetCore.Cryptography.KeyDerivation package
available via NuGet. It provides PBKDF2 algorithm with SHA-1, SHA-256, or SHA-512 hash functions, which is considerably better than
Rfc2898DeriveBytes
. The biggest advantage here is that the implementation is supplied by Microsoft, and while I cannot properly assess cryptographic diligence of Microsoft developers versus BCrypt.net or libsodium developers, it just makes sense to trust it because if you are running a .NET application, you are heavily relying on Microsoft already. We might also expect Microsoft to release updates if security issues are found. Hopefully.
To summarize the research up to this point, while PBKDF2 might be the least preferred algorithm of the four, the availability of Microsoft-supplied implementation trumps that, so the reasonable decision would be to use Microsoft.AspNetCore.Cryptography.KeyDerivation
.
The recent package at the moment targets .NET Standard 2.0, so available in .NET Core 2.0 or .NET Framework 4.6.1 or later. If you use earlier framework version, it is possible to use the previous version of the package, 1.1.3, which targets .NET Framework 4.5.1 or .NET Core 1.0. Unfortunately, it is not possible to use it in even earlier versions of .NET.
The documentation and the working example is available at learn.microsoft.com. However, do not copy-paste it as it is, there are still decisions a developer needs to make.
The first decision is what hash function to use. Available options include SHA-1, SHA-256, and SHA-512. Of those, SHA-1 is definitely too fast to be secure, SHA-256 is decent, but I would recommend SHA-512, because supposedly, its 64-bit operations usage makes it harder to benefit from GPU-based attacks.
Then, you need to choose the password hash output length and the salt length. It doesn't make sense to have output longer than the hash function output (e.g. 512 bits for SHA-512), and it would probably be the most secure to have it exactly like that. For the salt length, opinions differ. 128 bits should be enough, but in any case, the length longer than the hash output length surely doesn't provide any benefits.
Next, there is an iteration count. The bigger it is, the harder password hashes are to crack, but the longer it takes to log users in. I'd suggest to choose it so the hashing takes 0.25 - 1 seconds on the typical production system, and in any case, it should not be less than 10000.
Normally, you would get bytes array as salt and hash values. Use Base64 to convert them to strings. You can opt to use two different columns in the database, or combine salt and password in one column using a separator which is not encountered in Base64.
Don't forget to devise a password hashing storage in a way that allows to seamlessly move to a better hashing algorithm in future.