You probably have heard this many many times. “Thou shall not store passwords in clear text in your database.” Instead you should store hashes of the passwords. This is for security reasons, just in case some malicious hacker gets into your database, you want to make it hard for him to get the actual passwords, he will only get the hashes, and he can’t do much with those.
I had been looking for a simple, straight forward example or demo, explaining how to store hashes of passwords, but I didn’t succeed in finding one. Or let’s say I was a little lazy in doing the research.
So I decided to code my own simple implementation, and in doing so, hope to help someone else understand or just get a glimpse of the power of .Net 2.0 cryptography.
In this example we are going to create a simple system that will store user names and hashes of the user passwords in a simple table (we’ll use a generic dictionary in this case for simplicity) and then perform password verifications as we would in a real application.
The code samples are in VB.
Start a new project in Visual Studio 2005, give it the name of your choice, and perform the following imports.
Imports System.Security.Cryptography
Imports System.Text.Encoding
Then under Module1 type in the following code to create a new class and some variables which I will explain below shortly.
Public Class PasswordEncryptor
Private salt As Byte()
Private key As Rfc2898DeriveBytes
Private secretKey As Byte()
Private Provider As HMACSHA1
Private DataBank As Dictionary(Of String, String)
Explanation:
The Salt is a byte array which we are going to use to generate the key we need for our hashing algorithm.The bytes will come from a plain text string we will provide in the class construtor.It could also be a file.
The Rfc2898DeriveBytes Key is a new class in .Net Framework 2.0 which uses a password based key derivation system based on the HMACSHA1 algorithm.
The provider we are going to use, HMACSHA1, is a security class that uses the SHA1 function to compute a Hash-based Message Authentication Code (HMAC).This class accepts a key of any size and produces a hash sequence of 160 bits.
The databank is the generic dictionary we’ll use to store our usernames and hashes of the passwords. You could store them in a SQL server database or in any other database system, but for simplicity we are going to use a dictionary in this example.
Now let me explain the logic.
We are going to use two layers of security. First we are going to use a password, salt and a key size (iteration count) to generate a secure secret key.Then we will use this secret key to create our hashing algorithm and this is the algorithm we will use to generate the hashes of the passwords.
Now type in the following code for our class constructor.
Sub New(ByVal _salt As String, ByVal _password As String, _
ByVal _size As Integer)
‘use utf8 encoding to get the bytes from our salt
‘use the salt and the password to generate a key
‘generate the secret key using the key and size
‘use the secret key to create the Hashing Algorithm
Me.salt = UTF8.GetBytes(_salt)
Me.key = New Rfc2898DeriveBytes(_password, Me.salt)
Me.secretKey = Me.key.GetBytes(_size)
Me.Provider = New HMACSHA1(Me.secretKey)
DataBank = New Dictionary(Of String, String)
End Sub
Now that we have our algorithm we are going to create a procedure to generate the Password Hashes and save them in our database.The easy and simple way is to have the user provide a username and a password.We then hash the password, and save the hash against the username.
Type in the following code within the class to create the procedure.
Sub GenerateHash(ByVal Usename As String, _
ByVal password As String)
‘use utf8 encoding to read the password into a byte array
Dim data() As Byte = UTF8.GetBytes(password.ToCharArray)
‘then use our provider to compute the hash
Provider.ComputeHash(data)
‘convert the hash to string
Dim res As String = System.Convert.ToBase64String(Provider.Hash)
‘save the hash
DataBank.Add(Usename.ToLower, res)
End Sub
It is as simple as that.
What we need next is a way to check the hash of the password a user provides at login, against what we have in our database.For this we will create another function, which computes the hash of the provided password, compare it against the one we have in our database, and tell us if they match or not.
Function CheckPassword(ByVal username As String, _
ByVal password As String) As Boolean
‘the steps are the same as for previous sub
Dim data() As Byte = UTF8.GetBytes(password.ToCharArray)
Provider.ComputeHash(data)
Dim res As String = System.Convert.ToBase64String(Provider.Hash)
‘now check first if the user exists.
If Not DataBank.ContainsKey(username.ToLower) Then
Console.WriteLine(“User does not exist”)
Exit Function
Else
‘now compare the hashes
If DataBank(username.ToLower) = res Then
Return True
Else
Return False
End If
End If
End Function
And you are done. You will notice that I used Username.ToLower to avoid all the case sensitivity issues. Now let’s try and see if it works. In our main procedure we are going to instantiate the class with our salt( which is just some text) and a password, register one user, then run some password verifications.
Type the following code.
Sub Main()
Dim ps As New PasswordEncryptor(“Sodium Chloride is my salt”, _
“securepassword”, 16)
ps.GenerateHash(“Nmasao”, “r3m3mb3r”)
Console.WriteLine(“Passwords Match: {0}”, _
ps.CheckPassword(“Nmasao”, “r3m3mb3r”))
Console.Read()
End Sub
If you run this code, you should see the following output in the console window.
Passwords Match: True
If the password is verified you can then grant the user access to your application.
Now change the password to something else and run the code.
Console.WriteLine(“Passwords Match: {0}”, _
ps.CheckPassword(“Nmasao”, “yada_yada”))
This is what you should see.
Passwords Match: False
There are many issues to consider when dealing with passwords. And this post does not even scratch the top.It is up to you to dig, research and decide on the most convenient and secure way of dealing with user passwords.
For instance, since you do not store the user password, you should probably use a secret question and save it against the user name, and code some functionality to reset the user password in cases where users have lost or forgotten their password.
Important warning: You should never hard code passwords in your application. They can easily be read using some tools.Well, till next post, that’s it for now, I welcome your comments.