Press "Enter" to skip to content

ScottLilly.com Posts

C# code to store uniquely-salted, hashed passwords in the database and validate at login

For the web application I’m building, I want to keep the users’ data as secure as [reasonably] possible.  One way I’m doing that is by storing the password as a hashed value, with each account having a unique salt value for the hash.  This is useful to prevent rainbow table attacks – a common security issue for passwords stored as one-way hashed values.

Here’s the C# code I’m using to store a hashed password in the database, unique a unique salt value for each account.

For this project, the user’s account name is their email address.

User table structure (MS SQL Server 2008)

ColumnDatatypeNullable
IDuniqueidentifiernot null
EmailAddressvarchar(250)not null
Saltvarbinary(250)not null
Passwordbinary(16)not null

 

AccountAccessor.cs

using System;
using System.Data;
using System.Linq;
using System.Security.Cryptography;
using System.Web.Security;
using Resources;
namespace DataAccessors
{
    public class AccountAccessor : BaseAccessor, IAccountAccessor
    {
        public void CreateNewAccount(AccountManagementView userAccount)
        {
            if(AccountForThisEmailAddressExists(userAccount.EmailAddress))
            {
                throw new DuplicateRecordException(Literals.AnAccountForThisEmailAddressAlreadyExists, userAccount.EmailAddress);
            }
            byte[] salt = System.Text.Encoding.UTF8.GetBytes(Membership.GeneratePassword(100, 20));
            byte[] hashedPassword = CreateSaltedHashedPassword(salt, userAccount.Password);
            BuildInsertQueryForTable("User")
                .SetColumnToValue("ID", Guid.NewGuid())
                .SetColumnToValue("EmailAddress", userAccount.EmailAddress)
                .SetColumnToValue("Salt", salt)
                .SetColumnToValue("Password", hashedPassword)
                .ExecuteNonQuery();
        }
        public bool UserAccountIsValid(AccountManagementView userAccount)
        {
            DataSet results =
                BuildSelectQueryForTable("User")
                    .SelectAllColumns()
                    .Where("EmailAddress", SQLHydra.Enums.Comparators.EqualTo, userAccount.EmailAddress)
                    .ExecuteQuery();
            if(results.Tables[0].Rows.Count == 1)
            {
                byte[] salt = (byte[])results.Tables[0].Rows[0]["Salt"];
                byte[] hashedEnteredPassword = CreateSaltedHashedPassword(salt, userAccount.Password);
                byte[] hashedStoredPassword = (byte[])results.Tables[0].Rows[0]["Password"];
                return !hashedEnteredPassword.Where((t, i) => hashedStoredPassword[i] != t).Any();
            }
            return false;
        }
        private static byte[] CreateSaltedHashedPassword(byte[] salt, string password)
        {
            return new HMACMD5(salt).ComputeHash(System.Text.Encoding.UTF8.GetBytes(password));
        }
        private static bool AccountForThisEmailAddressExists(string emailAddress)
        {
            int accountsForThisEmailAddress =
                (int) BuildSelectQueryForTable("User")
                          .SelectCountOfAllRows("count")
                          .Where("EmailAddress", SQLHydra.Enums.Comparators.EqualTo, emailAddress)
                          .ExecuteScalarQuery();
            return (accountsForThisEmailAddress > 0);
        }
    }
}

What’s going on in the code

I’m using the GeneratePassword method from .Net’s Membership class.  You want to use a method like this that is cryptographically strong.  Don’t use a random GUID, or something generated from the Random class (which is not truly random).

Caveats

I’m using MD5 as the hashing method.  There are more secure methods.  However, MD5 is slower than most of the more secure methods.  Therefore, this makes it significantly slower for anyone running a brute force attack.  It’s a trade-off.

If you want to use a more secure hash method, you may need to make the password column’s length longer (e.g., SHA256 produces a 256-bit result).

By using a one-way hash, you don’t have a way to send a user their password, if they forget it.

The best way to handle forgotten passwords is to send the user an e-mail with a link to a page that will only be active for a certain length of time.  They go to that page and enter in a new password, which you then hash and store.

Additional information

To see why you want to use a method like this, read this article http://crackstation.net/hashing-security.htm.

1 Comment