Click here to Skip to main content
15,867,453 members
Articles / Security

Beginners guide to a secure way of storing passwords

Rate me:
Please Sign up or sign in to vote.
4.91/5 (81 votes)
20 Jul 2012CPOL7 min read 227.1K   2.1K   123   52
This article will explain how to securely store users passwords in a database.

TOC

Introduction

This article will explain how to store passwords in a databse the secure way, and how to create a simple login.

It's not an article about architecture or general security, but how to transport password securely in and out of database and how to store it there.

Background

The reason for writing this article is after reading a lot of questions here at codeproject and other forums. A lot the code out there is vulnerable for sql injections and the password is often stored in clear text. I will will try to explain how to deal with this in a simple but understandable way so even beginners can secure their data.

Using the code

First of all you will need somewhere to store usernames and passwords. In this article I will use Sql server, but it's the same technique for all types of databases.

I will not create a huge table keeping all sort of userdata, but simply three properties. UserId, Username and password.

To get started I will create a table to store users. I'll call this table "Users" and it will contain 3 columns. (I will add one more column later)

SQL
UserId       int (not null) Identity
Username     varchar(50)
Password     varchar(50)

-- Create script for Users table        
CREATE TABLE [Users](
    [UserId] [int] IDENTITY(1,1) NOT NULL,
    [Username] [varchar](50) NOT NULL,
    [Password] [varchar](50) NOT NULL
)

To add users to our database we can create a function to do this. A function which takes arguments username and password. I'll create a class "UserDatabase" to keep all my interaction with the Users table in one place.

C#
public class UserDatabase
{
    private static string connectionstring = "SERVER=127.0.0.1; DATABASE=MyDatabase; UID=***; PWD=***";

    public static bool AddUser(string username, string password)
    {
        // This function will add a user to our database

        SqlConnection con = new SqlConnection(connectionstring);
        using (SqlCommand cmd = new SqlCommand("INSERT INTO [Users] VALUES (@username, @password)", con))
        {
            // Add the input as parameters to avoid sql-injections
            // I'll explain later in this article.
            cmd.Parameters.AddWithValue("@username", username);
            cmd.Parameters.AddWithValue("@password", password);

            con.Open();
            cmd.ExecuteNonQuery();
            con.Close();
        }
        return true;
    }
}

Now we have a simple function to add users. It does not ckeck if username exists or anything, but simply inserts a new user.

So now we can add a webform and let the users register:

HTML
<form id="form1" runat="server">
    <asp:TextBox ID="txtUsername" runat="server"/>
    <asp:TextBox ID="txtPassword" runat="server" TextMode="Password"/>
    <asp:Button ID="btnAddUser" runat="server" Text="Submit" OnClick="btnAddUser_Click" />
</form>

And the code behind:

C#
protected void btnAddUser_Click(object sender, EventArgs e)
{
    string username = txtUsername.Text;
    string password = txtPassword.Text;

    bool result = UserDatabase.AddUser(username, password);
}

Now a user can fill in the form, click submit and the user and password will be stored in our database. The only problem is that the password will be stored as clear text and that is not ok!

If I fill out the form and hits submit this will get stored in our table:

UserId | Username        | Password
-------------------------------------------
1      | AlluvialDeposit | mySecretPassword

And that is what this article will help you avoid using hashed passwords.

What is a hash?

There are tons of articles about hashing and how they work. But I'll explain the very basics.

If you hash a string (or other data) you will receive a string that is impossible to transform back to it's original form. But the key thing is that you will always get the same result if you use the same hash algorithm.

If I hash (using sha1) my password "mySecretPassword" I will get the string "F032680299B077AFB95093DE4082F625502B8251" That string is impossible to revert back to the original value!

Hash a string is easy. This function will do the job

C#
public class Security
{
    public static string HashSHA1(string value)
    {
        var sha1 = System.Security.Cryptography.SHA1.Create();
        var inputBytes = Encoding.ASCII.GetBytes(value);
        var hash = sha1.ComputeHash(inputBytes);

        var sb = new StringBuilder();
        for (var i = 0; i < hash.Length; i++)
        {
            sb.Append(hash[i].ToString("X2"));
        }
        return sb.ToString();
    }
}

I'm using SHA-1 which will return a string with a length of 40 no matter what the input value is. If you hash a megabyte of plain text, the hash would still be 40 characters long. So yes, there is a theoretical way of producing 2 identical hash-values with 2 different input values. But that's rare! Very rare.

Add hash functionality

So if we look back on AddUser function in our UserDatabase class:

C#
// From the example above
cmd.Parameters.AddWithValue("@password", password);
        
// Now we switch this to:
cmd.Parameters.AddWithValue("@password", Security.HashSHA1(password));

So if you go back to your browser and try to register a new user, the table will get another row with userId 2. But this time the users password is hashed!

UserId | Username        | Password
-------------------------------------------
1      | AlluvialDeposit | mySecretPassword
2      | AlluvialDeposit | F032680299B077AFB95093DE4082F625502B8251

If someone hack our database they will not be able to see our users password! or?

Well that is not true. A hash is impossible to reverse, but hackers often use large table of hashed strings. If a hacker has a generated database with 10 million random passwords (and the most common used ones), he could easily look in his database for the the same string "F032680299B077AFB95093DE4082F625502B8251" to see if he finds any match. Unfortunately he will often find a match!

Another problem with this approach is that two users with the same password will have the same hashed password as well. That could also be a security risk.

Fortunately this is quite easy to avoid. The thing is that you will need some kind of unique string for each user which are used when you hash the password. That is called "password salt". I use a System.Guid to do the job.
This Guid must be stored in the user table so that we can use it to validate the password when the user wants to authenticate.

Add column "UserGuid" to "Users" table with the datatype uniqueidentifier

SQL
ALTER TABLE [Users]
ADD UserGuid uniqueidentifier NULL

We have to do a few changes to our AddUser() method as well:

C#
public static bool AddUser(string username, string password)
{
    // This function will add a user to our database

    // First create a new Guid for the user. This will be unique for each user
    Guid userGuid = System.Guid.NewGuid();
        
    // Hash the password together with our unique userGuid
    string hashedPassword = Security.HashSHA1(password + userGuid.ToString());

    SqlConnection con = new SqlConnection(connectionstring);
    using (SqlCommand cmd = new SqlCommand("INSERT INTO [Users] VALUES (@username, @password, @userguid)", con))
    {
        cmd.Parameters.AddWithValue("@username", username);
        cmd.Parameters.AddWithValue("@password", hashedPassword); // store the hashed value
        cmd.Parameters.AddWithValue("@userguid", userGuid); // store the Guid

        con.Open();
        cmd.ExecuteNonQuery();
        con.Close();
    }
    return true;
}

If we fire up our AddUser.aspx again and store another user with the same password as previous ones, we will get this result:

UserId | Username        | Password                                  | UserGuid
--------------------------------------------------------------------------------------
1      | AlluvialDeposit | mySecretPassword                          | null
2      | AlluvialDeposit | F032680299B077AFB95093DE4082F625502B8251  | null
3      | AlluvialDeposit | 42444F86F185B7F229DD155E8BCF2A9E6D06453C  | E089D2FC-97DB-4DA5-A13C-9FCBBD8B7E95

And if we add another user with the same criterias once more:

UserId | Username        | Password                                  | UserGuid
--------------------------------------------------------------------------------------
1      | AlluvialDeposit | mySecretPassword                          | null
2      | AlluvialDeposit | F032680299B077AFB95093DE4082F625502B8251  | null
3      | AlluvialDeposit | 42444F86F185B7F229DD155E8BCF2A9E6D06453C  | E089D2FC-97DB-4DA5-A13C-9FCBBD8B7E95
4      | AlluvialDeposit | D329E99795960F93857CA49D5FB0A8F141C34671  | C6B73B2D-F704-4250-81C4-EFAEC5DB5F54

User id 3 and 4 has the same password! But in the database it's impossible to tell! And a hacker cannot check our passwordHash to see if he finds any matches with his pregenerated data.

It is possible for a hacker to find out what the password is, but that will take a lot of time! And he will have to do a time consuming operation for each user in the datasbase.

So now our data is safe (enough).

Don't forget to check if username already exists when a new user signs up!

Authenticate users

Add this function to your class "UserDatabase" This function will give you a userId based on username and password.

C#
public static int GetUserIdByUsernameAndPassword(string username, string password)
{
    // this is the value we will return
    int userId = 0;

    SqlConnection con = new SqlConnection(connectionstring);
    using (SqlCommand cmd = new SqlCommand("SELECT UserId, Password, UserGuid FROM [Users] WHERE username=@username", con))
    {
        cmd.Parameters.AddWithValue("@username", username);
        con.Open();
        SqlDataReader dr = cmd.ExecuteReader();
        while(dr.Read())
        {
            // dr.Read() = we found user(s) with matching username!

            int dbUserId = Convert.ToInt32(dr["UserId"]);
            string dbPassword = Convert.ToString(dr["Password"]);
            string dbUserGuid = Convert.ToString(dr["UserGuid"]);

            // Now we hash the UserGuid from the database with the password we wan't to check
            // In the same way as when we saved it to the database in the first place. (see AddUser() function)
            string hashedPassword = Security.HashSHA1(password + dbUserGuid);

            // if its correct password the result of the hash is the same as in the database
            if(dbPassword == hashedPassword)
            {
                // The password is correct
                userId = dbUserId;
            }
        }
        con.Close();
    }

    // Return the user id which is 0 if we did not found a user.
    return userId;
}

What we do here is to get the user from the database based on username. If we find a matching user we will have to check whether the password is correct. The password is hashed with password salt so we have to do the same procedure (hashing) with the password the user typed, as we did when the user signed up. If the password is the same, the hash function will return the same hash-result as the hash-password in the database. We compare these two hashed password to see if they match.

Now we can authenticate the user using this simple code:

C#
int userId = UserDatabase.GetUserIdByUsernameAndPassword("AlluvialDeposit", "mySecretPassword");
if(userId > 0)
{
    // Username and password are correct and we know which user!
}

Create a simple login-form and validate username/password in codebehind:

HTML
<form id="form1" runat="server">
    <asp:TextBox ID="txtUsername" runat="server"/>
    <asp:TextBox ID="txtPassword" runat="server" TextMode="Password"/>
    <asp:Button ID="btnLogin" runat="server" Text="Submit" OnClick="btnLogin_Click" />               
    <hr />        
    <asp:Label runat="server" id="lblLoginResult"/> 
</form>
C#
protected void btnLogin_Click(object sender, EventArgs e)
{
    var username = txtUsername.Text;
    var password = txtPassword.Text;

    int userId = UserDatabase.GetUserIdByUsernameAndPassword(username, password);
    if(userId > 0)
    {
        // Now you can put users id in a session-variable or what you prefer
        // and redirect the user to the protected area of your website.
        lblLoginResult.Text = string.Format("You are userId : {0}", userId);
    }
    else
    {
        lblLoginResult.Text = "Wrong username or password";
    }
}

What about password recovery

Sometimes users forget their passwords. We will have to deal with it but we can't read a users password and then we can't send en email telling the user his/her password (that would be a bad idea anyway!).

So if the user has lost the password, we will have to send an email with a link to a page where the user can set a new password and store it the same way as we did when the user signed up.

A very basic explanation about sql-injections

Sql-injections is one of the most used ways to hack a website. It's easy to do and there are a lot of websites which are vulnerable for this kind of attack.

Sql-injection is to tamper your sql-query. If I am able to put some of my strings into your sql-query, then you have a problem!

Lets say you have a login-form with this code:

C#
protected void btnLogin_Click(object sender, EventArgs e)
{
    SqlConnection con = new SqlConnection(connectionstring);
    string sql = "SELECT UserId FROM [Users] WHERE username='" + TextBox1.Text + "'";
    using (SqlCommand cmd = new SqlCommand(sql, con))
    {
        SqlDataReader dr = cmd.ExecuteReader();
        // Read som data and return userid..
    }
}

The variable sql will hold the query to get executed against our database. So if a user writes "hello" in TextBox1, the sql would be:

SQL
SELECT UserId FROM [Users] WHERE username='hello'

That would not be a problem. But if a user writes '; DELETE FROM [Users] our sql would be:

SQL
SELECT UserId FROM [Users] WHERE username=''; DELETE FROM [Users]

Now all your users are deleted!

That is just an easy example to show what is happening. I clever user could write something in that textbox to log in as any user in your database.

If you always use SqlParameters (like in this article) you will avoid the most obvious way of sql-injections.

Summary

This is an example and it covers the very basics. But if you follow this guidelines you will take a huge step in the right direction. It's a matter of your users security!

Good luck with your secure user-database Smile | :)

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior) Aurum AS
Norway Norway
Microsoft Certified Solutions Developer (MCSD)

Personal website:
stian.net

My projects:
CRM1.no - A free to use norwegian crm software
Fakturax - A free to use norwegian invoice software
Timeføring.no - A free to use norwegian timereg software
MittUtlegg - A free to use norwegian software for receipts
SupportWeb - A free to use norwegian software customersupport

Comments and Discussions

 
PraiseExcellent! Pin
Etherjazz7-Nov-19 12:41
Etherjazz7-Nov-19 12:41 
Questionreset email Pin
Member 1418376713-May-19 3:22
Member 1418376713-May-19 3:22 
PraiseGood article but SHA1 is not secure anymore Pin
tahirk55519-Nov-17 7:57
professionaltahirk55519-Nov-17 7:57 
QuestionHow to decrypt the password from the database using your code Pin
vmoin25-Sep-17 0:53
vmoin25-Sep-17 0:53 
PraiseA well written article ...keep writing Pin
Shibasis Sengupta2-Jul-16 23:17
professionalShibasis Sengupta2-Jul-16 23:17 
GeneralExcellent Article Pin
Joel Antonio Hernandez Gastelum28-May-15 7:19
Joel Antonio Hernandez Gastelum28-May-15 7:19 
Questionhelp Pin
Member 1125830224-Nov-14 3:47
Member 1125830224-Nov-14 3:47 
AnswerRe: help Pin
Programazing12-Mar-16 18:18
Programazing12-Mar-16 18:18 
AnswerRe: help Pin
StianSandberg12-Mar-16 23:36
StianSandberg12-Mar-16 23:36 
Questionhelp Pin
Member 1058892022-Feb-14 20:22
Member 1058892022-Feb-14 20:22 
AnswerRe: help Pin
StianSandberg23-Feb-14 11:05
StianSandberg23-Feb-14 11:05 
GeneralMy vote of 5 Pin
jgakenhe18-Feb-14 6:07
professionaljgakenhe18-Feb-14 6:07 
GeneralMy vote of 5 Pin
Sabyasachi Misra20-Sep-13 23:08
professionalSabyasachi Misra20-Sep-13 23:08 
GeneralMy vote of 5 Pin
Thanks787229-Apr-13 19:24
professionalThanks787229-Apr-13 19:24 
AnswerRe: My vote of 5 Pin
StianSandberg29-Apr-13 19:53
StianSandberg29-Apr-13 19:53 
Thank you Smile | :)
QuestionThanks Pin
Venkat Krishna Turlapati10-Apr-13 17:25
Venkat Krishna Turlapati10-Apr-13 17:25 
AnswerRe: Thanks Pin
StianSandberg21-Apr-13 21:45
StianSandberg21-Apr-13 21:45 
GeneralMy vote of 5 Pin
Renju Vinod31-Jan-13 22:13
professionalRenju Vinod31-Jan-13 22:13 
GeneralRe: My vote of 5 Pin
StianSandberg21-Apr-13 21:45
StianSandberg21-Apr-13 21:45 
QuestionNicely done Pin
R. Giskard Reventlov8-Nov-12 7:08
R. Giskard Reventlov8-Nov-12 7:08 
GeneralMy vote of 5 Pin
Kenneth Haugland18-Aug-12 13:54
mvaKenneth Haugland18-Aug-12 13:54 
GeneralRe: My vote of 5 Pin
StianSandberg19-Aug-12 22:48
StianSandberg19-Aug-12 22:48 
GeneralMy vote of 5 Pin
zzbsforever1-Aug-12 17:32
zzbsforever1-Aug-12 17:32 
GeneralRe: My vote of 5 Pin
StianSandberg14-Aug-12 20:39
StianSandberg14-Aug-12 20:39 
QuestionGreat! Pin
Sander Rossel1-Aug-12 7:45
professionalSander Rossel1-Aug-12 7:45 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.