Password security best practices (with examples in C#)
A brief rundown of some of the common mistakes people make with password security, and then an overview of some 'good practices' (with examples in C#).

I'm not an expert in security or cryptography. I'm not even a web developer. But I see web developers doing password security wrong ALL THE TIME, and it really gets my goat.

This blog post will give a brief rundown of some of the common mistakes people make, and then an overview of some "good practices", with examples in C#.

Why improper password storage is bad

I've seen people say "why should we waste our time storing passwords properly, we aren't a bank, so it doesn't matter if our users get their accounts compromised". The problem is, your users are only human; they probably use the same password for multiple services. If your password database gets compromised, the crackers now have a list of usernames, e-mail addresses, and passwords that they can go and try on many other sites across the internet. Storing passwords improperly puts your users at risk. Also, it will make you a laughing stock if you ever get found out!

DO NOT: store passwords in plain text

This one should be pretty obvious, and needs no explanation. If you store your passwords in plain-text, and your password database gets compromised, there is absolutely nothing standing between the crackers and a big, juicy list of all of the usernames/e-mails/passwords in your system. In addition, everyone will laugh at you and probably say mean things about you.

DO NOT: store passwords in a reversible format (ie. don't encrypt your passwords)

I know the word 'encrypted' sounds secure, but you really shouldn't store passwords in any form that is reversible. If your system gets compromised, your 'secure' key will probably be taken just as quickly and easily as your password database is. End result: your 'encrypted' passwords are just as insecure as plain text ones.

DO NOT: hash your passwords with a general purpose hash function (such as MD5, SHA, etc.)

Hashing your passwords is the best thing to do. Using a general purpose hash function (such as MD5, SHA, etc.) is NOT. In the past, 'conventional wisdom' was that hashing+salting your passwords was the way to go. These days, if you use a general-purpose hash function, you're doomed. Modern GPUs can calulate hashes very, very fast. Anyone who wants to throw some GPUs at your password database will be able to brute force billions of passwords per second. All the salt in the world won't save you at this point - nobody bothers with rainbow tables any more. Brute force will have you cracked in no time.

Doing things the right way

Alright, so you're not allowed to store passwords in plain text, you're not allowed to encrypt them, and you're not allowed to hash+salt them with a general purpose hash function. What's left, you ask? You should use a hash function that was DESIGNED FOR PASSWORD HASHING! The best examples are PBKDF, BCRYPT and SCRYPT. These hash functions are designed to be slow, and they are designed so that they can be slowed down in the future (by increasing their 'work factor'), so that they keep pace with increasing hardware power. As an example, one of these functions may take 100ms to hash a single password. Compare this to the generate purpose functions above, where millions or billions of passwords can be hashed in the same unit of time. Your users won't care if there's 100ms worth of overhead on the login process (which they only have to go through on an infrequent basis). Crackers who have compromised your password database WILL care when they are stuck trying to brute force 10 passwords per second, instead of the billions of passwords per second that they would otherwise be able to try when a general purpose hash function was used.

How do I do this in .NET?

UPDATE 2021: Wow, this post is so old that I'd forgotten I'd even written it. The .NET ecosystem is a lot different now than it was back in 2013 when this post was originally written. If you're reading this post in 2021 or later, and are wondering how do manage passwords in your ASP.NET Core web application - just use Microsoft.AspNetCore.Identity. It's a lot easier than trying to follow my original advice. However, for posterity, my original 2013 advice was as follows:

Which hash function should be used?

As mentioned above, either BCRYPT, SCRYPT, or PBKDF are fine. I strongly recommend using PBKDF, as it has an implementation built in to the .NET base class library (Rfc2898DeriveBytes). This means that you can safely assume the IMPLEMENTATION of the algorithm is correct and secure, without having to audit it yourself. A lot of .NET developers use PBKDF for this reason, rather than having to constantly audit and verify third-party BCRYPT/SCRYPT implementations.

Generating a salt of a certain length:

byte[] GenerateSalt(int length)
{
    var bytes = new byte[length];

    using (var rng = new RNGCryptoServiceProvider())
    {
        rng.GetBytes(bytes);
    }

    return bytes;
}

Generating a password hash using PBKDF:

byte[] GenerateHash(byte[] password, byte[] salt, int iterations, int length)
{
    using (var deriveBytes = new Rfc2898DeriveBytes(password, salt, iterations))
    {
        return deriveBytes.GetBytes(length);
    }
}

Putting it all together:

Store the following in your user database (alongside any additional data you need):

  • Password salt
  • Password hash
  • Iterations / work factor

When a user creates an account:

  • Generate a new salt.
  • Generate a hash using the generated salt and the provided password.
  • Save the salt, hash, and work factor in the database.

When a user tries to log in:

  • Generate a hash using the provided password and the stored salt and work factor.
  • If the hash generated above matches the stored hash, the password was correct; otherwise, the password was incorrect!

If you want to increase the work factor at a later date, write a script that will run on user login to:

  • Verify the user's password by comparing the hash generated using the provided password, the stored salt, and the stored work factor with the stored hash.
  • Generate a new password hash using the provided password, the stored salt, and the new (increased) work factor, and store the new password hash and the new work factor in the database.

Additional resources


Posted by Matthew King on 2 December 2013
Permission is granted to use all code snippets under CC BY-SA 3.0 (just like StackOverflow), or the MIT license - your choice!
If you enjoyed this post, and you want to show your appreciation, you can buy me a beverage on Ko-fi or Stripe