Return to HomePage




Hash a Password Using a Random Salt (C#)


Applies To

* ASP.NET 2.0
* C#

Summary

The purpose of this code snippet is to demonstrate how to implement secure password persistence using a cryptographic hashing algorithm with a randomly generated "salt" (or "nonce") value. Cryptographic hashing algorithms are one-way encryption algorithms used to store sensitive data in a non-readable format. A salt can be used in conjunction with cryptographic hashing to add additional entropy to encrypted values and to protect against pre-computed hash or dictionary attacks on a compromised hash value.


Objectives

* Protect user credentials
* Avoid storing user passwords
* Protect against certain brute-force attacks on a compromised hash value
* Generate a cryptographically random value for the salt that cannot be predicted
* Add enough entropy to the password hash to increase the difficulty of a cracking attempt exponentially

Scenarios

* Application makes use of a dedicated user account management system and stores passwords
* Application stores a "Secret Question/Answer" credential for password reset operations
* Application requires storage of highly sensitive data (social security number, credit card number, etc) but does not need to retrieve that data


Solution Example


public static byte[] CreatePasswordHash(string password)
{
		    	// Convert the string password value to a byte array
		    	byte[] passwordData = [UnicodeEncoding.ASCII.GetBytes(password);]
	

		    	// Create a 4-byte salt using a cryptographically secure random number generator
		    	byte[] saltData = new byte[4];
		    	[RNGCryptoServiceProvider] rng = new [RNGCryptoServiceProvider();]
		    	[rng.GetNonZeroBytes(saltData);] 
	

		    	// Append the salt to the end of the password
		    	byte[] [saltedPasswordData] = new byte[passwordData.Length + saltData.Length];
		    	Array.Copy(passwordData, 0, [saltedPasswordData,] 0, passwordData.Length);
		    	Array.Copy(saltData, 0, [saltedPasswordData,] passwordData.Length, saltData.Length);
	

		    	// Create a new SHA-1 instance and compute the hash 
		    	SHA1Managed sha = new SHA1Managed();
		    	byte[] hashData = [sha.ComputeHash(saltedPasswordData);]
	

		    	// Optional - add salt bytes onto end of the password hash for storage
		    	bool APPEND_SALT_TO_HASH = true;
	

		    	if (APPEND_SALT_TO_HASH)
		    	{
		        		byte[] [hashSaltData] = new byte[hashData.Length + saltData.Length];
		        		Array.Copy(hashData, 0, [hashSaltData,] 0, hashData.Length);
		        		Array.Copy(saltData, 0, [hashSaltData,] hashData.Length, saltData.Length);
		        		return [hashSaltData;]
		    	}
		    	else
		    	{
		        		return hashData;
		    	}
	
}

Problem Example

The following code snippet shows password hashing without the use of a salt and using a weaker hashing algorithm.

// password is obtained from the user as a C# string
string password = Request.Form"password";

// Convert the string password value to a byte array
byte[] passwordData = UnicodeEncoding.ASCII.GetBytes(password);

// Create a new MD5 instance and compute the hash
MD5CryptoServiceProvider md5 = new MD5CryptoServiceProvider();
byte[] hashData = md5.ComputeHash(passwordData);

* Hash values are vulnerable to pre-computed hash attacks
* Depending on password value, password hash may also be vulnerable to a dictionary attack
* MD5 offers less encryption strengtgh than SHA-1 and has recently been "broken" by cryptography researchers


Test Case

The following classes must be included in any project making use of the sample code provided above:

using System.Security.Cryptography;

Execute a test encryption and comparison of a salted password hash using the following test case methods:

static void Main(string[] args)
{
		    	// Create a Hash and compare to two subsequent hashes
		    	byte[] hash = CreatePasswordHash("foobar");
		    	Console.WriteLine("\nCreated new salted hash for 'foobar'");            
		    	Console.WriteLine("foobar produces same hash:\t" + ComparePasswordToHash("foobar", [hash).ToString());]
		    	Console.WriteLine("fo0bar produces same hash:\t" + ComparePasswordToHash("f0obar", [hash).ToString());]
	
}

public static bool ComparePasswordToHash(string password, byte[] hashData)
{
		    	// First, pluck the four-byte salt off of the end of the hash
		    	byte[] saltData = new byte[4];
		    	Array.Copy(hashData, hashData.Length - saltData.Length, saltData, 0, saltData.Length);
	

		    	// Convert Password to bytes
		    	byte[] passwordData = [UnicodeEncoding.ASCII.GetBytes(password);]
	

		    	// Append the salt to the end of the password
		    	byte[] [saltedPasswordData] = new byte[passwordData.Length + saltData.Length];
		    	Array.Copy(passwordData, 0, [saltedPasswordData,] 0, passwordData.Length);
		    	Array.Copy(saltData, 0, [saltedPasswordData,] passwordData.Length, saltData.Length);
	

		    	// Create a new SHA-1 instance and compute the hash 
		    	SHA1Managed sha = new SHA1Managed();
		    	byte[] [newHashData] = [sha.ComputeHash(saltedPasswordData);]
	

		    	// Add salt bytes onto end of the password hash for storage
		    	byte[] [newHashSaltData] = new byte[newHashData.Length + saltData.Length];
		    	[Array.Copy(newHashData,] 0, [newHashSaltData,] 0, [newHashData.Length);]
		    	Array.Copy(saltData, 0, [newHashSaltData,] [newHashData.Length,] saltData.Length);
	

		    	// Compare and return
		    	return [(Convert.ToBase64String(hashData).Equals(Convert.ToBase64String(newHashSaltData)));]
	
}


Expected Result

Created new salted hash for 'foobar'
foobar produces same hash: True
fo0bar produces same hash: False


More Information

Password hash and salt values should always be securely protected in storage. An attacker able to compromise a specific hash and salt value from a database may succeed in using other types of brute-force attacks against the compromised hash.

In the example given, the four-byte salt would require an attacker to maintain 4.3 trillion values for every given plaintext value. Assuming the victim required passwords of only 4 alphabetical characters in
length with no other complexity requirements (a very weak password policy by our standards), defeating a four-byte salt would require the attacker to have a database of 2 x 10^15 precomputed hashes handy. Assuming each of these hashes only required one byte to store, this would require 2 petabytes of storage.


Additional Resources

* See "Cryptopgraphy" section in "Security Guidelines: .NET Framework 2.0" at http://msdn.microsoft.com/library/en-us/dnpag2/html/PAGGuidelines0003.asp?frame=true#pagguidelines0003_cryptography

Attributes

* Applies To: .NET Framework 2.0, C#
* Category: Cryptography
* Author: Jonathan Bailey




Return to HomePage
Microsoft Communities