Return to HomePage



Hash a Password Using a Random Salt (VB)




Applies to

* ASP.NET 2.0
* VB
* Server-side

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


Solution Example

Function CreatePasswordHash(password As String) As Byte()
		   		' Convert the string password value to a byte array
		   		Dim passwordData As Byte() = [UnicodeEncoding.ASCII.GetBytes(password)]
	

		   		' Create a 4-byte salt using a cryptographically secure random number generator
		   		Dim saltData(4) As Byte
		   		Dim rng As New [RNGCryptoServiceProvider()]
		   		[rng.GetNonZeroBytes(saltData)]
	

		   		' Append the salt to the end of the password
		   		Dim [saltedPasswordData(passwordData.Length] + saltData.Length) As Byte
		   		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 
		   		Dim sha As New SHA1Managed()
		   		Dim hashData As Byte() = [sha.ComputeHash(saltedPasswordData)]
	

		   		' Optional - add salt bytes onto end of the password hash for storage
		   		Dim APPEND_SALT_TO_HASH As Boolean = True
	

		   		If APPEND_SALT_TO_HASH Then
		      			Dim [hashSaltData(hashData.Length] + saltData.Length) As Byte
		      			Array.Copy(hashData, 0, [hashSaltData,] 0, hashData.Length)
		      			Array.Copy(saltData, 0, [hashSaltData,] hashData.Length, saltData.Length)
		      			Return [hashSaltData]
		   		Else
		      			Return hashData
		   		End If
	
End Function


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 VB string
	
Dim password As String = Request.Form("password")

' Convert the string password value to a byte array
Dim passwordData As Byte() = UnicodeEncoding.ASCII.GetBytes(password)

' Create a new MD5 instance and compute the hash
Dim md5 As New MD5CryptoServiceProvider()
Dim hashData As Byte() = 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:

Imports System.Security.Cryptography
Imports System.Text

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

Sub Main(ByVal args() As String)
		    		' Create a Hash and compare to two subsequent hashes
		    		Dim hash() As Byte =  CreatePasswordHash("foobar") 
		    		Console.WriteLine("Created new salted hash for 'foobar'")            
		    		Console.WriteLine("foobar produces same hash:" + ComparePasswordToHash("foobar", [hash).ToString())]
		    		Console.WriteLine("fo0bar produces same hash:" + ComparePasswordToHash("f0obar", [hash).ToString())]
	
End Sub

Function ComparePasswordToHash(ByVal password As String, ByVal hashData() As Byte) As Boolean
		    		' First, pluck the four-byte salt off of the end of the hash
		    		Dim saltData() As Byte =  New Byte(4) {} 
		    		Array.Copy(hashData, hashData.Length - saltData.Length, saltData, 0, saltData.Length)
	

		    		' Convert Password to bytes
		    		Dim passwordData() As Byte =  [UnicodeEncoding.ASCII.GetBytes(password)] 
	

		    		' Append the salt to the end of the password
		    		Dim [saltedPasswordData()] As Byte =  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 
		    		Dim sha As SHA1Managed =  New SHA1Managed() 
		    		Dim [NewHashData()] As Byte =  [sha.ComputeHash(saltedPasswordData)] 
	

		    		' Add salt bytes onto end of the password hash for storage
		    		Dim [NewHashSaltData()] As Byte =  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)))]
	
End Function


Expected Result

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


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


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, VB
* Category: Cryptography
* Author: Jonathan Bailey
Microsoft Communities