Hashing passwords with a password based key derivation function in .NET

In this post we saw a basic hashing technique using a password and a salt. We added an extra random set of bytes to the password and hashed the combined byte array instead of just the password bytes. We can store the salt along with the hash in our database. The main purpose of adding a salt to the password is to increase its entropy which more or less means randomness.

Hashing the password with an extra salt like that may still not be as secure as we think it is. The processing power of today’s fast computers and the increasing size of available rainbow tables keep pushing the limits of what’s available to crack with brute force attacks. One way to increase the difficulty of cracking a password is to keep hashing its hash in an iterative manner. Password-based key derivation functions help us achieve that and we’ll see an example of their usage in this post.

A password-based key derivation function – PBKDF2 – takes a password and a salt like we saw before and repeats the hashing process multiple times to produce a derived key. The added password hashing complexity makes it more difficult to use rainbow tables to find the original password. There are at least 2 other important terms related to PBKDF2:

  • PKCS: RSA public key cryptographic standards, PKCS #5 Version 2.0 to be exact, where PBKDF2 is part of the standard
  • RFC2898: the password-based cryptography specification Internet engineering task force where PKCS #5 Version 2.0 is outlined. We’ll soon see how this task force is reflected in the appropriate .NET class name

The implementation

We’ll use the below object to store the salt and the message digest to be returned from the hashing function:

public class HashWithSaltResult
{
	public string Salt { get; }
	public string Digest { get; set; }

	public HashWithSaltResult(string salt, string digest)
	{
		Salt = salt;
		Digest = digest;
	}
}

Here’s is our random number generator class which will produce the salt – for details check out this link:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Security.Cryptography;

namespace RandomNumberGenerator
{
    public class RNG
    {
		public string GenerateRandomCryptographicKey(int keyLength)
		{			
			return Convert.ToBase64String(GenerateRandomCryptographicBytes(keyLength));
		}

		public byte[] GenerateRandomCryptographicBytes(int keyLength)
		{
			RNGCryptoServiceProvider rngCryptoServiceProvider = new RNGCryptoServiceProvider();
			byte[] randomBytes = new byte[keyLength];
			rngCryptoServiceProvider.GetBytes(randomBytes);
			return randomBytes;
		}
	}
}

PBKDF2, or RFC2898 is represented by the Rfc2898DeriveBytes class in the System.Security.Cryptography namespace. Here comes the PKCS password hasher with Rfc2898DeriveBytes in action:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Security.Cryptography;
using RandomNumberGenerator;

namespace HashingAlgos
{
	public class PKCS
	{
		public HashWithSaltResult HashPasswordWithPkcs(string plainPassword, int roundOfHashIterations, int saltLengthBytes)
		{
			RNG rng = new RNG();
			byte[] saltBytes = rng.GenerateRandomCryptographicBytes(saltLengthBytes);
			Rfc2898DeriveBytes pbkdf = new Rfc2898DeriveBytes(plainPassword, saltBytes, roundOfHashIterations);
			byte[] derivedBytes = pbkdf.GetBytes(32);
			return new HashWithSaltResult(Convert.ToBase64String(saltBytes), Convert.ToBase64String(derivedBytes));
		}
	}
}

We start by creating a salt with the provided byte size. Then we use the Rfc2898DeriveBytes object to calculate the derived key with a number of iterations. The Rfc2898DeriveBytes.GetBytes method accepts an integer that represents the byte length of the produced key. Finally we return the calculated values.

Here’s an example of creating a hash with 100, 10000 and 50000 iterations. Note that the execution time increases with the number of iterations so be aware of the trade-off between increased security and code performance.

private static void TestPkcs()
{
	string password = "ultra_safe_P455w0rD";
	PKCS pkcs = new PKCS();
	HashWithSaltResult hashResult100Iterations = pkcs.HashPasswordWithPkcs(password, 100, 32);
	HashWithSaltResult hashResult10000Iterations = pkcs.HashPasswordWithPkcs(password, 10000, 32);
	HashWithSaltResult hashResult50000Iterations = pkcs.HashPasswordWithPkcs(password, 50000, 32);

	Console.WriteLine(hashResult100Iterations.Salt);
	Console.WriteLine(hashResult100Iterations.Digest);
	Console.WriteLine();
	Console.WriteLine(hashResult10000Iterations.Salt);
	Console.WriteLine(hashResult10000Iterations.Digest);
	Console.WriteLine();
	Console.WriteLine(hashResult50000Iterations.Salt);
	Console.WriteLine(hashResult50000Iterations.Digest);
}

Here’s the output from top to bottom:

fARVMvYREG5I8UDMU2Czl6Snb5m2hwZ1V779Q2i6rDo=
haVvyQfRHIg3KN9mns9Zv4b4TkYrPBtUK72k+BBpbq4=

dtDSOVJwpEBnnXzMQuFF6voEEMGxYnlmNP4/lfjrib0=
/3t5/0qRaPb5/xTg0ygndBfonLvWMeGGcdkytlFcCRM=

xoJTP6vC2+iZyh1Xqf5IPPB9ZGHXrq7kfNo4ND5dl6s=
mXT3MTOAbZr7w6KbN12CmDoA2IviYe08x+sxi37x9K8=

You can view the list of posts on Security and Cryptography here.

About Andras Nemes
I'm a .NET/Java developer living and working in Stockholm, Sweden.

Leave a comment

Elliot Balynn's Blog

A directory of wonderful thoughts

Software Engineering

Web development

Disparate Opinions

Various tidbits

chsakell's Blog

WEB APPLICATION DEVELOPMENT TUTORIALS WITH OPEN-SOURCE PROJECTS

Once Upon a Camayoc

Bite-size insight on Cyber Security for the not too technical.