Problem
You need to protect login credentials during transmission over the network and when they are stored within a database.
Solution
Use password hashing and salting with the .NET FormsAuthentication class to control user authentication and access to the application.
The schema of table TBL0508 used in this solution is shown in Table 5-5.
Table 5-5. TBL0508 schema
Column name |
Data type |
Length |
Allow nulls? |
---|---|---|---|
UserName |
nvarchar |
50 |
No |
PasswordHash |
nvarchar |
50 |
No |
PasswordSalt |
nvarchar |
50 |
No |
The sample code contains two event handlers:
Create Button.Click
Creates a GUID-based salt and generates a hash of the password concatenated with the salt for a user-specified password. The username, password hash, and salt are inserted into a database.
Login Button.Click
Retrieves the salt and the hash of the password and salt from the database for the specified username. The user-entered password is concatenated with the retrieved salt and the hash is generated. If the hash matches the hash retrieved from the database, the user is authenticated.
The C# code is shown in Example 5-8.
Example 5-8. File: ADOCookbookCS0508.aspx.cs
// Namespaces, variables, and constants using System; using System.Configuration; using System.Web.Security; using System.Data; using System.Data.SqlClient; private const String TABLENAME = "TBL0508"; // . . . private void createButton_Click(object sender, System.EventArgs e) { // Create and display the password salt. String passwordSalt = Guid.NewGuid().ToString( ); passwordSaltLabel.Text = passwordSalt; // Create and display the password hash. String passwordHash = FormsAuthentication.HashPasswordForStoringInConfigFile( passwordTextBox.Text + passwordSalt, "md5"); passwordHashLabel.Text = passwordHashDBLabel.Text = passwordHash; // Insert UserName with the password hash and salt into the database. String sqlText = "INSERT " + TABLENAME + "(UserName, PasswordHash, PasswordSalt) " + "VALUES ('" + userNameTextBox.Text + "', '" + passwordHash + "', '" + passwordSalt + "')"; SqlConnection conn = new SqlConnection( ConfigurationSettings.AppSettings["DataConnectString"]); SqlCommand cmd = new SqlCommand(sqlText, conn); conn.Open( ); try { if(cmd.ExecuteNonQuery( ) == 1) statusLabel.Text = "User created."; else statusLabel.Text = "Could not create user."; } catch(SqlException) { statusLabel.Text = "Could not create user."; } finally { conn.Close( ); } } private void loginButton_Click(object sender, System.EventArgs e) { bool isAuthenticated = false; // Get the password hash and salt for the user. String sqlText = "SELECT PasswordHash, PasswordSalt FROM " + TABLENAME + " WHERE UserName = '" + userNameTextBox.Text + "'"; SqlConnection conn = new SqlConnection( ConfigurationSettings.AppSettings["DataConnectString"]); SqlCommand cmd = new SqlCommand(sqlText, conn); conn.Open( ); SqlDataReader dr = cmd.ExecuteReader( ); // Get the DataReader first row containing user's password and salt. if(dr.Read( )) { // Get and display password hash and salt from the DataReader. String passwordHashDB = passwordHashDBLabel.Text = dr.GetString(0); String passwordSalt = passwordSaltLabel.Text = dr.GetString(1); // Calculate password hash based on the password entered and // the password salt retrieved from the database. String passwordHash = passwordHashLabel.Text = FormsAuthentication.HashPasswordForStoringInConfigFile( passwordTextBox.Text + passwordSalt, "md5"); // Check whether the calculated hash matches the hash retrieved // from the database. isAuthenticated = (passwordHash == passwordHashDB); } conn.Close( ); if(isAuthenticated) statusLabel.Text = "Authentication succeeded."; else statusLabel.Text = "Authentication failed."; }
Discussion
Persisting a user's password can be made more secure by first hashing the password. This means that an algorithm is applied to the password to generate a one-way transformationor hash of the password making it statistically infeasible to recreate the password from the hash.
A hash algorithm creates a small binary value of fixed length from a larger binary value of an arbitrary length. The hash value is a statistically unique compact representation of the original data. A hash value can be created for and transmitted together with data. The hash can be recreated at a later time and compared to the original hash to ensure that the data has not been altered . To prevent the message from being intercepted and replaced along with a new hash, the hash is encrypted using the private key of an asymmetric key algorithm. This allows the hash to be authenticated as having come from the sender. For more information about symmetric and asymmetric key algorithms, see the discussion in Recipe 5.7. The .NET Framework classes that implement hash algorithms are:
In the sample, the user enters his password, the password is hashed , and then the combination of user ID and password hash are compared to values stored persistently such as in a database table. If the pairs match, the user is authenticated, without comparing the actual password. Because the hash algorithm is a one-way algorithm, the user's password cannot be recreated even if unauthorized access is gained to the persistent store where the user's password hash is stored.
The .NET Framework, as part of the FormsAuthentication class, provides the method HashPasswordForStoringInConfigFile( ) that can hash a password using either SHA1 or MD5 algorithms. The method is easy to call. It takes two arguments, the password and the hash algorithm, and returns a string containing the password hash.
Security is never perfect and this technique is no exception. It can be compromised by a dictionary attack where hash values for most commonly used passwords are generated. When these values are compared with the hash of the password and a match is found, the password is then known. To thwart the dictionary attack, a random string referred to as salt is concatenated with the original password before generating the hash value. The salt is stored together with the hash of the password and salt. This makes a dictionary attack much more difficult to perform.
This web page should be used in conjunction with forms-based authentication. Additionally, this page should be accessed securely (i.e., https to protect the plaintext password from client to server).
The most secure technique is useless if the password policy does not prevent users from choosing easy to guess passwords, or if security is compromised by users who write passwords down on notes attached to their computer monitors , for example.
Connecting to Data
Retrieving and Managing Data
Searching and Analyzing Data
Adding and Modifying Data
Copying and Transferring Data
Maintaining Database Integrity
Binding Data to .NET User Interfaces
Working with XML
Optimizing .NET Data Access
Enumerating and Maintaining Database Objects
Appendix A. Converting from C# to VB Syntax