How to store and use passwords in .NET

Edit: I uploaded a sample project to GitHub here. End Edit.

This is really one of my pet peeves. The last five years of stolen user accounts really got the community on its feet and the internet is oozing of advice of how to do this. Most of them well intended but badly implemented. Many of the bad ones use good practices but put them all in a blender. Not all algorithms are meant to be mixed. My version I intend to keep clean and use mainstream encryption.

First what parts are we talking about?

  1. Storing passwords
  2. Using passwords

What good is a stored password if you cannot use it afterwards. This, however, doesn’t mean that you ever should be able to recreate (or decrypt) the original password. Always compare scrambled password to scrambled password. And yes, the downside is that the user never can ask to get his password sent thru email in case they forgot. There’s more to this than just number crunching the hash. How to set the cookie properly is one place where good intentions crumble…

Anyway, this is my take…

I won’t go into to much detail of all the inner workings of hashing algorithms and such. But two things is important, salting and stretching. Both of which is taken care of when using the standard algorithms i .NET.

Here is a the code that generates the salt and the salted password

public static HashedPassword Generate(string password)
{
byte[] _salt = new byte[8];
using (RNGCryptoServiceProvider csp = new RNGCryptoServiceProvider())
{
csp.GetBytes(_salt);
}
byte[] _password = System.Text.Encoding.UTF8.GetBytes(password);
Rfc2898DeriveBytes k1 = new Rfc2898DeriveBytes(_password, _salt, 10000);
var _saltedPasswordHash = k1.GetBytes(24);
return new HashedPassword()
{
Password = Convert.ToBase64String(_saltedPasswordHash),
Salt = Convert.ToBase64String(_salt)
};
}
public struct HashedPassword
{
public string Password { get; set; }
public string Salt { get; set; }
}
view raw gistfile1.cs hosted with ❤ by GitHub

The key areas in this code is the use of RNGCryptoServiceProvider for generating the hash. Don’t create you own randomizer! And don’t ever use the same salt for all users and then hide it somewhere.

The main difference between a normal random number generator (RNG) and Cryptographically Secure Pseudo Random Number Generator (CSPRNG) – that’s a mouthful – lies in the predictability. Normal random numbers looks random but they really aren’t.

Ok, so now we have a salt. Next we are going to use Microsofts PBKDF2-implementation Rfc2898DeriveBytes to generate the key. The key in this case, is a hash that could be used as an parameter to other cryptographic stuff like the TripleDES encryption algorithm for encrypting a file.

It is worth noting that the key is not an encrypted version of the password.

A PBKDF (Password Based Key Derivation Function) is in it self a CSPRNG using the password and salt to create its Initialization Vector. After that you can use it to generate as many bytes as you like.

Sequential calls to GetBytes will not return the same bytes but the next bytes in the sequence.

Rfc2898DeriveBytes k1 = new Rfc2898DeriveBytes(_password, _salt, 10000);
var _saltedPasswordHash = k1.GetBytes(12);
Debug.WriteLine(Convert.ToBase64String(_saltedPasswordHash));
_saltedPasswordHash = k1.GetBytes(12);
Debug.WriteLine(Convert.ToBase64String(_saltedPasswordHash));
Rfc2898DeriveBytes k2 = new Rfc2898DeriveBytes(_password, _salt, 10000);
_saltedPasswordHash = k2.GetBytes(24);
Debug.WriteLine(Convert.ToBase64String(_saltedPasswordHash));
view raw gistfile1.cs hosted with ❤ by GitHub

This will generate this output:

b9oLeVK9RKNatt7X
G1MQqtPYCZnlabPR

b9oLeVK9RKNatt7XG1MQqtPYCZnlabPR

It is important to remember to also store the salt alongside with the password in your database. Why, you say, are you storing the salt? If the database get snatched the hacker also has the salt.

Yes correct, but first of all you need it when validating at login time and furthermore the salting makes it impossible to use rainbow tables.

The hacker has to calculate every possible password, salt it, hash it and then do the compare. Note that we also did stretching, we ran the hashing 10000 times.
When our user tries to log in again, we take his newly entered password and hash it using the same salt which we retrieved from the database.

public static bool Validate(string passwordHash, string saltHash, string enteredPassword)
{
byte[] _password = System.Text.Encoding.UTF8.GetBytes(enteredPassword);
Rfc2898DeriveBytes keyEntered = new Rfc2898DeriveBytes(_password, Convert.FromBase64String(saltHash), 10000);
return Convert.ToBase64String(keyEntered.GetBytes(24)) == passwordHash;
}
view raw Validate hosted with ❤ by GitHub

Using this is pretty straightforward

var keyAndSalt = Hash.Generator.Generate("P@ssword2013");
bool isEqual1 = Hash.Generator.Validate(keyAndSalt.Password, keyAndSalt.Salt, "Password2013");
//isEqual1 == false
bool isEqual2 = Hash.Generator.Validate(keyAndSalt.Password, keyAndSalt.Salt, "P@ssword2013");
//isEqual2 == true
view raw gistfile1.cs hosted with ❤ by GitHub

Now, as a final note about using the passwords:

Ok, so the user is now logged in to your system. The password is not stored anywhere and the hash is safe with you.

Somehow you have to remember during the length of the session that he or she is auhenticated. Normally you do this using browser cookies. Pretty easy to do, but if you just add it as a normal cookie it is susceptible to eavesdropping and hijacking of the session.

Three things to remember:

  • Always use https from the login screen and on. When the user clicks “login” you switch to https and stay there.
  • Set the authentication cookie to be Secure and HttpOnly to mitigate most of the threats like XSS.
    Secure means that it will only be sent when doing https calls. HttpOnly means the the cookie will only be used by the browser. Javascript cannot see it.
  • Do not use mixed content, i.e. you serve the html securely via https but some script och images get fetched through normal http. You will leak cookies! However, the steps above will normally stop this.

Leave a comment