Keeping Passwords in a Database Secure
By Edgar Sánchez MCP (Web Forms & Windows Forms)
Published: 2/16/2004
Reader Level: Beginner Intermediate
Rated: 4.55 by 31 member(s).
Tell a Friend
Rate this Article
Printable Version
Discuss in the Forums

When we need to authenticate users in an ASP.NET site, the safest and easiest way to go is to take advantage of the Windows operating system authentication. Basically, you configure your site at Internet Information Server (IIS) for Windows authentication, as shown in FIGURE 1.

FIGURE 1: Check mark Integrated Windows authentication

You also set your ASP.NET site to use Windows authentication in the Web.config file like this (this is actually the default):

<authentication mode="Windows" />

You are all set. Internet Explorer will ask you for your credentials (username and password) when needed and the user information will be available at the this.User.Identity property of any page for you to use.

Anonymous Access

Unfortunately, there are situations in which we can't use these facilities. For example, in an ASP.NET application that serves thousands of users in the Internet, it is not practical to create a Windows user for each person accessing your site. In such scenarios, it is customary to allow anonymous access to our IIS site, as shown in FIGURE 2:

FIGURE 2: Allowing Anonymous access

Then set our ASP.NET site for Windows forms authentication in the Web.config file:

<authentication mode="Forms" />

Plenty of places discuss the forms authentication method and most of them check user credentials against a database table. Unfortunately, all of the examples that I have seen use a database similar to the one shown in FIGURE 3.

FIGURE 3: Passwords are stored as clear text in this database.

Of course, the big security risk here is the fact that the passwords are stored as clear text: Anybody that can get a glimpse of the Users table will have a bunch of user credentials to access your site. When faced with this problem, many programmers quickly start writing their own encryption algorithms, which usually have at least two flaws:

  1. Homegrown encryption algorithms are not hard to break (for an informed hacker, anyway).
  2. For storing passwords, you don't really need encryption, a message digest is enough.
Using Hashes

So, what is a message digest? Imagine a function that calculates a fixed output (usually 128 or 160 bits) from a variable length input (in our case, the password string). The fixed output is the message digest or hash of the password, and the function that calculates the digest is called the hashing function. Serious hashing functions have two very useful characteristics:

  1. It's not computationally feasible to calculate the original message from the digest; that is, given a password's hash not even the most powerful super-computer will be able to infer the original string! So we are far more secure if we store the password's hash instead of the clear text in the Users table.
  2. It's not computationally feasible to find two messages that will generate the same digest; that is, no super-computer will be able to create a mock password that produces the same hash as the original password.

The second characteristic leads to the mechanism for checking a user's password: You take the string that she submitted in the password text box and "hash" it. If this digest is equal to the one stored in the database, you can be sure that the user knows the correct password, without ever having to store the original password in our database. Remember the best way to keep a secret is not to keep it.

Let's see a small sample program that takes the Users table and hashes all the contents of the password column:

using System;
using System.Data;
using System.Data.SqlClient;
using System.Web.Security;
namespace LogicStudio.Security.PasswordStorage
{
  class SimplisticHashing
  {
    private const string ConnString =
        "Server=(local);Database=SpySales;Integrated Security=true";
    static void Main()
    {
      using (SqlConnection con = new SqlConnection(ConnString))
      {
        SqlDataAdapter da =
          new SqlDataAdapter("select id, password from Users", con);
        DataSet ds = new DataSet();
        da.Fill(ds, "Users");
        foreach (DataRow row in ds.Tables["Users"].Rows)
        {
          row["password"] =
            FormsAuthentication.HashPasswordForStoringInConfigFile (
              row["password"] as string,
              "SHA1" );
        }
        new SqlCommandBuilder(da);
        da.Update(ds.Tables["Users"]);
      }
    }
  }
}

Let's focus on the Main() method. I simply read all rows from the Users table into the ds DataSet, then looped every row, and used HashPasswordForStoringInConfigFile() for creating a SHA1 digest for the password. Finally, all of the table is saved back to the database. Some items worth noting:

  1. The FormsAuthentication class belongs to the System.Web.Security namespace, but don't let that stop you from using it in a console application (as in the example) or even a Windows application. Alas, you have to reference the System.Web assembly in any project where you use FormsAuthentication.
  2. The HashPasswordForStoringInConfigFile() was created for saving hashed passwords in a config file (Web.config or App.config), but it can be used in any place where you require to create a message digest from a string (although some people say this method was really designed for winning the prize to the longest-named method in the FCL).
  3. I used the SHA1 algorithm, which produces a 160-bit digest. The other alternative is MD5, which generates a 128-bit digest. Both algorithms are pretty much the standards for hashing in the Internet.

Forgive me for some really bad programming techniques in the example: hard-coding the connection string, using a select command instead of calling a stored procedure, using SqlCommandBuilder() instead of an update stored procedure, etc., I just wanted to focus in the secure storage of passwords and I am sure you will replace my poor practices with correct and safe code. Anyway, after running the SimplisticHashing application, the Users table looks like FIGURE 4.

FIGURE 4: The hash application at work

A simple method for checking a pair of credentials against this table would be:

public bool CheckCredentials(string username, string password)
{
  using (SqlConnection con = new SqlConnection(ConnectionString))
  {
    con.Open();
    SqlCommand cmd = new SqlCommand(
      "select password from Users where username=@givenUsername",
      con );
    cmd.Parameters.Add("@givenUsername", username);
    string storedHashedPassword = cmd.ExecuteScalar() as string;
    if (storedHashedPassword == null)
      return false;
    string givenHashedPassword =
      FormsAuthentication.HashPasswordForStoringInConfigFile (
        password,
        "SHA1" );
    return storedHashedPassword == givenHashedPassword;
  }
}

The heart of the method is the use of HashPasswordForStoringInConfigFile() to hash the given password before comparing it to the stored hash, and the rest is pretty much boilerplate code. Again, pardon the same flaws that I pointed for my previous code, but do note that at least I used parameters instead of concatenation for building the select command and I also used ExecuteScalar() for some extra performance.

Salting a Hash

So far, it seems we solved our problem pretty well, but there is a reason the hashing application is called SimplisticHashing. Quick-eyed readers surely noticed in the "hashed" Users table that Homer and Lisa have the "same" hashed password. Of course, this is not a failure of the hashing algorithm, but a consequence of the fact that both had the same original password (Simpson). This shows a new, although smaller, security hole: If you know the password of a user, and some other user happens to have the same hash, then you know both have the same password. Furthermore, a hacker can exploit this fact with something called a dictionary attack.

What a hacker could do is to create a big dictionary (say 500,000 entries) of the words people use as passwords (Mom, Yankees, etc.), hash it, and then compare every hashed entry with every hashed password in the Users table. If he hits a coincidence, he's got a password. This is one of the reasons it's strongly recommended to create complex passwords with #@$! characters. But, as not everybody follows this advice, we better protect our passwords from a dictionary attack by salting our hash.

To salt a hash, we generate a random string that we prefix to the clear password before hashing it. Then we save both, the salt and the hashed password, in the Users table. In this way, for the hacker to try a dictionary attack, he must hash his 500,000 entries with the salt of every user row, so his attack takes far, far longer and his probabilities of success diminish drastically. An example for salt hashing the passwords of the Users tables is shown in the following code:

using System;
using System.Data;
using System.Data.SqlClient;
using System.Security.Cryptography;
using System.Web.Security;
namespace LogicStudio.Security.PasswordStorage
{
  class SaltedHashing
  {
    private const string ConnString =
        "Server=(local);Database=SpySales;Integrated Security=true";
    private const int SaltByteSize = 16;
    static void Main()
    {
      using (SqlConnection con = new SqlConnection(ConnString))
      {
        SqlDataAdapter da = new SqlDataAdapter(
            "select id, salt, password from Users", con );
        DataSet ds = new DataSet();
        da.Fill(ds, "Users");
        foreach (DataRow row in ds.Tables["Users"].Rows)
        {
          RNGCryptoServiceProvider rng =
              new RNGCryptoServiceProvider();
          byte[] buff = new byte[SaltByteSize];
          rng.GetBytes(buff);
          string salt = Convert.ToBase64String(buff);
          row["salt"] = salt;
          row["password"] =
            FormsAuthentication.HashPasswordForStoringInConfigFile (
              salt + (row["password"] as string),
              "SHA1" );
        }
        new SqlCommandBuilder(da);
        da.Update(ds.Tables["Users"]);
      }
    }
  }
}

The lines worth noting are the ones where we use the RNGCryptoServiceProvider to create the salt. The RNGCryptoServiceProvider.GetBytes() method creates an array of random bytes suitable for cryptographic chores. Basically, the random values created by this method are "more randomic" than those provided by the System.Random class, so we won't get repeated salts. We then convert the bytes to a string, put it into the salt column, and prepend it to the password before hashing it. Note that RNGCryptoServiceProvider lives in the System.Security assembly, so you must add a reference to it in your project. The salted Users table is shown in FIGURE 5.

FIGURE 5: The salted Users table

Now Homer and Lisa have different password hashes and salts (but of course their passwords are the same: Simpson), and we are much less vulnerable to a dictionary attack. One may think that using a different salt for every user and then storing it together with the hash is a waste and a risk (maybe just one salt is enough and we can save it in a safe place), but this just opens another security hole. Wherever we save the salt, a hacker may get hold of it and then he can use his dictionary again. For this reason, it is general practice to follow the methods shown in this article. Finally, here's an example of the validation method for the salted password users:

public bool CheckCredentials(string username, string password)
{
  using (SqlConnection con = new SqlConnection(ConnString))
  {
    con.Open();
    SqlCommand cmd = new SqlCommand(
     "select salt,password from Users where username=@givenUsername",
      con );
    cmd.Parameters.Add("@givenUsername", username);
    SqlDataReader dr = cmd.ExecuteReader();
    if (!dr.Read())
      return false;
    string salt = dr["salt"] as string;
    string storedHashedPassword = dr["password"] as string;
    string givenHashedPassword =
      FormsAuthentication.HashPasswordForStoringInConfigFile (
        salt + password,
        "SHA1" );
    return storedHashedPassword == givenHashedPassword;
  }
}

This time, we don't have the luxury of using ExecuteScalar() as we need the hashed password and the salt, so we bring the whole row through a data reader and we prepend the salt to the given password before hashing it. In this way, we can compare the hashed values correctly.

To summarize, although it's not quite evident on the surface, the .NET Framework does offer strong support for storing passwords in a secure way and once you know how to do it, it's fairly easy. From now on, don't ever save a clear text password in your database, hash it (with salt) using FormsAuthentication and RNGCryptoServiceProvider.



Marketplace
(Sponsored Links)
What are the green links?
   



 
Copyright © 2007 CMP Tech LLC |
Privacy Policy (4/10/06) | Your California Privacy Rights (4/10/06) | Terms of Service | Advertising Info | About Us | Help