3.3.1 Hashing the Passwords
Let us consider an application where we need to maintain a database of usernames and passwords.
Naturally, the passwords need to be stored in a secure manner: even if the database is
hacked into or stolen, the passwords should still be hard for the perpetrator to retrieve.
Storing the passwords in clear text is, obviously, unacceptable. Encrypting
the passwords with a symmetric cipher is better but still not perfect: symmetric
encryption requires a key, and the security of that key can be compromised.
Besides, the passwords would still be
accessible to the developers and system administrators involved with the application,
which to a certain extent violates user privacy.
Using a one-way hash function solves these problems. Instead of storing the password itself,
we will store its hash value. To authenticate a user, the password he submits during login is
also hashed and compared with the value stored in the database. If the values match the user
is authenticated.
This approach keeps the passwords safe
even if the database is compromised, requires no keys,
and respects user privacy. However, it has one drawback: a forgotten password cannot be
recovered, only reset.
3.3.2 Preventing Dictionary Attacks with Salt
Even a hash-encrypted password database is not entirely secure. A hacker can compile a list of,
say, 1,000,000 most commonly used passwords and compute a hash function from all of them.
He can then get hold of your user account database and compare the hashed passwords in
the database with his own list to see what matches. This is a dictionary attack
and it can be very successful.
To make the dictionary attack more difficult, salt is used.
Salt is a random string that is concatenated with passwords before being operated on
by the hash function. The salt value is then stored in the user database together
with the result of the hash function. Using salt makes dictionary attacks practically
impossible as a hacker would have to compute the hashes for all possible salt values.
However, salt does not help make attacks on individual passwords any harder,
therefore it is important to use passwords that are hard to guess.
3.3.3 Sample Application
The following two code samples illustrate the use of hash in a user account management application:
the first one adds a user account to the database, and the second
authenticates a user against the database.
The sample database aspencrypt.mdb included with the installation contains the table Users
with the following simple layout:
user_name | user_password | salt |
peter | 5CAEF2F36DFB65D56AAF9AE21D0DE6BFD35B8249 | LIDWSBPVFN |
The following code sample adds a new user account:
VBScript |
<%
If Request("Create") <> "" Then
If Request("Username") = "" Then
Response.Write "Username not specified."
Response.End
End If
If Request("Password") = Request("Password2") Then
' Generate random salt (10 characters)
Randomize
Salt = ""
For i = 1 to 10
Salt = Salt & chr(int(Rnd * 26) + 65) '65 is ASCII for "A"
Next
' Calculate Hash of Password + Salt
Set CM = Server.CreateObject("Persits.CryptoManager")
Set Context = CM.OpenContext("", True)
Set Hash = Context.CreateHash
Hash.AddText Request("Password") & Salt
HashValue = Hash.Value.Hex
' Save username, hashed value and salt in the database
strConnect = "DRIVER={Microsoft Access Driver (*.mdb)};DBQ=" & _
Server.MapPath(".") & _
"\..\db\aspencrypt.mdb"
set Conn = Server.CreateObject("adodb.connection")
Conn.Open strConnect
SQL = "insert into Users(user_name,user_password,salt) values('" & _
Request("Username") & _
"', '" & HashValue & "', '" & Salt & "')"
Conn.Execute SQL
Conn.Close
Set Conn = Nothing
Response.Write "Account was successfully created."
Else
Response.Write "Password was not correctly confirmed."
End If
End If
%>
<FORM ACTION="03_addusers.asp" METHOD="POST">
<TABLE>
<TR><TD>Username:</TD>
<TD><INPUT TYPE="TEXT" NAME="Username" VALUE="<% = Request("Username") %>">
</TD></TR>
<TR><TD>Password:</TD>
<TD><INPUT TYPE="PASSWORD" NAME="Password"></TD></TR>
<TR><TD>Confirm Password:
</TD><TD><INPUT TYPE="PASSWORD" NAME="Password2"></TD></TR>
<TR><TD COLSPAN=2><INPUT TYPE="Submit" NAME="Create" VALUE="Create Account">
</TD></TR>
</TABLE>
</FORM> |
C# |
public void CreateAccount(object sender, System.EventArgs args)
{
if( Username.Text == "" )
{
txtResult.Text = "Username not specified.";
return;
}
if( String.Compare( Password.Text, Password2.Text ) != 0 )
{
txtResult.Text = "Password was not correctly confirmed.";
return;
}
// Generate random salt (10 characters A to Z)
StringBuilder strSalt = new StringBuilder();
Random rnd = new Random();
for( int i = 0; i < 10; i++ )
{
strSalt.Append( Convert.ToChar( 65 + ( rnd.Next() % 26 ) ) );
}
// Calculate hash of the password + salt
ICryptoManager objCM = new CryptoManager();
ICryptoContext objContext = objCM.OpenContext( "", true, Missing.Value );
ICryptoHash objHash = objContext.CreateHash( Missing.Value );
objHash.AddText( Password.Text + strSalt );
// Save all that in the database
OleDbConnection objConn = new OleDbConnection();
String strConnStr = "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" +
Server.MapPath(".") + @"\..\db\aspencrypt.mdb";
objConn.ConnectionString = strConnStr;
objConn.Open();
String strSQL = String.Format(
"insert into Users(user_name,user_password,salt) values('{0}','{1}','{2}')",
Username.Text, objHash.Value.Hex, strSalt );
OleDbCommand objCmd = new OleDbCommand(strSQL, objConn);
objCmd.ExecuteNonQuery();
} |
For the sake of brevity, the username uniqueness check has been omitted from this code sample.
Click the links below to run this code sample:
http://localhost/aspencrypt/manual_03/03_addusers.asp
http://localhost/aspencrypt/manual_03/03_addusers.aspx
The following code sample authenticates a user against the database:
VBScript |
<%
If Request("ValidateIt") <> "" Then
' Obtain user record (hashed value and salt) by username
strConnect = "DRIVER={Microsoft Access Driver (*.mdb)};DBQ=" & _
Server.MapPath(".") & "..\..\db\aspencrypt.mdb"
set Conn = Server.CreateObject("adodb.connection")
Conn.Open strConnect
set rs = Server.CreateObject("adodb.recordset")
SQL = "select user_password, salt from Users where user_name = '" &_
Request("Username") & "'"
rs.Open SQL, Conn
If Not rs.EOF Then
HashValue = rs("user_password")
Salt = rs("Salt")
' Calculate Hash of specified password + Salt from DB
Set CM = Server.CreateObject("Persits.CryptoManager")
Set Context = CM.OpenContext("", True)
Set Hash = Context.CreateHash
Hash.AddText Request("Password") & Salt
HashValue2 = Hash.Value.Hex
If HashValue = HashValue2 Then
Response.Write "User authenticated."
Else
Response.Write "Invalid Password."
End If
Else
Response.Write "User not found."
End If
End If
%>
<FORM ACTION="03_validate.asp" METHOD="POST">
<TABLE>
<TR><TD>Username:</TD><TD><INPUT TYPE="TEXT" NAME="Username"></TD></TR>
<TR><TD>Password:</TD><TD><INPUT TYPE="PASSWORD" NAME="Password"></TD></TR>
<TR><TD COLSPAN=2>
<INPUT TYPE="Submit" NAME="ValidateIt" VALUE="Validate Account"></TD></TR>
</TABLE>
</FORM>
|
C# |
public void ValidateAccount(object sender, System.EventArgs args)
{
// Obtain user record (hashed value and salt) by username
OleDbConnection objConn = new OleDbConnection();
String strConnStr = "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" +
Server.MapPath(".") + @"\..\db\aspencrypt.mdb";
objConn.ConnectionString = strConnStr;
objConn.Open();
String strSQL = String.Format(
"select * from Users where user_name = '{0}'",
Username.Text );
OleDbCommand objCmd = new OleDbCommand(strSQL, objConn);
OleDbDataReader objReader = objCmd.ExecuteReader();
if( objReader.Read() )
{
String strPassword = objReader["user_password"].ToString();
String strSalt = objReader["salt"].ToString();
// Calculate hash of the password + salt
ICryptoManager objCM = new CryptoManager();
ICryptoContext objContext = objCM.OpenContext( "", true,
Missing.Value );
// Calculate hash of submitted password + Salt
ICryptoHash objHash = objContext.CreateHash( Missing.Value );
objHash.AddText( Password.Text + strSalt );
// Do they match?
if( objHash.Value.Hex == strPassword )
{
txtResult.Text = "User authenticated.";
}
else
{
txtResult.Text = "Invalid password.";
}
}
else
{
txtResult.Text = "User not found.";
}
} |
Click the links below to run this code sample:
http://localhost/aspencrypt/manual_03/03_validate.asp
http://localhost/aspencrypt/manual_03/03_validate.aspx