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:
- Homegrown encryption algorithms are not hard to break (for an informed hacker,
anyway).
- 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:
- 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.
- 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:
- 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.
- 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).
- 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.