Chapter 3: One-way Hash Functions
Contents
3.1 What is a One-way Hash Function?
A one-way hash function is a cryptographic algorithm that turns an arbitrary-length input into a fixed-length binary value, and this transformation is one-way, that is, given a hash value it is statistically infeasible to come up with a document that would hash to this value. Also it is virtually impossible to find two different documents that hash to the same value (such a pair of documents is referred to as a "collision".)
There are three widely used hash algorithms: MD5, SHA and SHA-256. MD5 produces a 128-bit hash, SHA a 160-bit hash, and SHA-256 a 256-bit hash.
Note: As of 2005, MD5 is no longer considered secure for digital signing purposes as it has been shown to lack collision resistance. However, it is still widely used in various security protocols and applications for key generation purposes.
MD5 and SHA are supported by all cryptographic providers, while SHA-256 is only supported by the Microsoft Enhanced RSA and AES Cryptographic Provider.
This chapter will describe two practical applications for one-way hash functions: secure password storage and file "fingerprinting".
3.2 Computing Hash with AspEncrypt
- Create a CryptoHash object.
- Add a text string or file to it. This step may be repeated several times.
- Retrieve the result in a desired format (Hex, Base64 or binary.)
The following code snippet computes the SHA hash value of the string "some text":
Set Context = CM.OpenContext("", True)
Set Hash = Context.CreateHash
Hash.AddText "some text"
Response.Write Hash.Value.Hex
ICryptoContext objContext = objCM.OpenContext( "", true, Missing.Value );
ICryptoHash objHash = objContext.CreateHash( Missing.Value );
objHash.AddText( "some text" );
Response.Write( objHash.Value.Hex );
The CreateHash method accepts an optional HashAlgorithm argument. It is calgSHA by default.
To calculate SHA-256, we need to pass the appropriate parameter (calgSHA256) to CreateHash, and also use OpenContextEx to connect to the Microsoft Enhanced RSA and AES Cryptographic Provider:
Set Context = CM.OpenContextEx( _
"Microsoft Enhanced RSA and AES Cryptographic Provider", "", True)
Set Hash = Context.CreateHash( calgSHA256 )
...
ICryptoContext objContext = objCM.OpenContextEx(
"Microsoft Enhanced RSA and AES Cryptographic Provider",
"", true, Missing.Value);
ICryptoHash objHash = objContext.CreateHash( CryptoAlgorithms.calgSHA256 );
...
The Hash.Value property returns a CryptoBlob object containing the computed hash value. This blob object can be queried using the Hex, Base64 or Binary properties as described in previous chapters.
Once the hash value is retrieved from the CryptoHash object, you can no longer call AddText on it until the object is reset by calling the Reset method. This way, multiple hash values can be calculated using the same CryptoHash object.
3.3 Secure Password Storage
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:
The following code sample adds a new user account:
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>
{
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:
The following code sample authenticates a user against the database:
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>
{
// 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:
3.4 Computing Hash Function of Files
Since it is practically impossible to find two documents that would have the same hash value, a document's hash, often referred to as a "fingerprint", can be used to uniquely identify this document. If you are developing a document repository, such as a database of images, and want to prevent the same document to appear in your database twice, you can store the document's thumbprint together with the document itself. This way, every time a new document is to be added you can easily check whether it already exists in your database by computing the new file's hash value and looking for a match in your database.
To calculate the hash value of a file, the method AddFile of the CryptoHash object should be used. This method accepts the physical path to the file being hashed:
Set Context = CM.OpenContext( "", True )
Set Hash = Context.CreateHash
Hash.AddFile "c:\path\file.ext"
Value = Hash.Value.Hex
ICryptoContext objContext = objCM.OpenContext( "", 1, Missing.Value );
ICryptoHash objHash = objContext.CreateHash( Missing.Value );
objHash.AddFile(@"c:\path\file.ext");
String strValue = objHash.Value.Hex;
The CryptoHash object can also be used to compute the hash value of a binary array via the method AddBinary.
3.5 Hash-based Message Authentication Code (HMAC)
3.5.1 What is HMAC?
The Message Authentication Code (MAC) function is essentially a hash function with two arguments instead of one: a secret key and an arbitrary-length message. The MAC protects both the message's data integrity and authenticity. For the MAC to work, all parties involved in the secure exchange of messages must share a secret key.
Conceptually, the MAC is simply a hash function applied to the secret key and message concatenated together, as follows (the "+" sign denotes concatenation):
MAC(Secret, Message) = Hash(Secret + Message)
The MAC is then sent alongside the message. The whole "envelope" is often encrypted.
|
The recipient of the MAC-equipped message concatenates his own copy of the secret key with the message and computes his own MAC. If the MAC values match, the message is authenticated and its data integrity is verified. A MAC is therefore similar to a digital signature but is based on the shared knowledge of a secret key instead of public-key cryptography.
In a real world, the simple formula shown above is not used as it is vulnerable to various cryptographic attacks. A more complicated formula, referred to as HMAC, is used instead. HMAC uses two nested invocations of the hash functions as well as two concatenations and two XOR operations, as follows:
HMAC(Secret, Message) = Hash((Secret ^ opad) + Hash((Secret ^ ipad) + Message))
The "^" symbol denotes a bitwise XOR operation, and opad and ipad are byte sequences 0x5c5c5c... and 0x363636... respectively.
The HMAC function is considered secure and resistant to most cryptographic attacks. It is heavily used in the Secure Socket Layer (SSL) and Transport Layer Security (TLS) protocols, among other areas.
HMAC can be based on any hash algorithm such as MD5, SHA or SHA256. The HMAC functions based on MD5, SHA, etc. are often denoted as HMAC_MD5, HMAC_SHA, etc. The length of the HMAC output equals that of the underlying hash function (16 bytes for HMAC_MD5, 20 bytes for HMAC_SHA, etc.)
3.5.2 AspEncrypt's HMAC Support
Starting with Version 2.6, AspEncrypt is capable of computing the HMAC function via the CryptoContext method ComputeHmac. This method expects a hash algorithm such as calgMD5 or calgSHA as the first argument, and CryptoBlob objects containing the secret key and message as the 2nd and 3rd arguments, respectively. The method returns an instance of the CryptoBlob object populated with the HMAC value.
The following code snippet computes the HMAC_MD5 and HMAC_SHA functions of the secret "my secret key" and message "Hello World!!!". Note that HMAC_SHA256, HMAC_SHA384 and HMAC_SHA512 require the Microsoft Enhanced RSA and AES Cryptographic Provider.
Set Context = CM.OpenContext( "", True )
Set KeyBlob = CM.CreateBlob
KeyBlob.Ansi = "my secret key"
Set MessageBlob = CM.CreateBlob
MessageBlob.Ansi = "Hello World!!!"
Set Hmac_MD5 = Context.ComputeHmac( calgMD5, KeyBlob, MessageBlob )
Set Hmac_SHA = Context.ComputeHmac( calgSHA, KeyBlob, MessageBlob )
Response.Write Hmac_MD5.Hex & "<BR>" & Hmac_SHA.Hex
ICryptoContext objContext = objCM.OpenContext( "", true, Missing.Value );
ICryptoBlob objKeyBlob = objCM.CreateBlob();
objKeyBlob.Ansi = "my secret key";
ICryptoBlob objMessageBlob = objCM.CreateBlob();
objMessageBlob.Ansi = "Hello World!!!";
ICryptoBlob objHmac_MD5 = objContext.ComputeHmac(
CryptoAlgorithms.calgMD5, objKeyBlob, objMessageBlob );
ICryptoBlob objHmac_SHA = objContext.ComputeHmac(
CryptoAlgorithms.calgSHA, objKeyBlob, objMessageBlob );
txtResult.Text = objHmac_MD5.Hex + "<BR>" + objHmac_SHA.Hex;
The expected result is:
E0C1A89DB5CCB48A07FA728554E200F95C647D9B