How to Hash Data with Salt
/////////////////////////////////////////////////////////////////////////////// // SAMPLE: Hashing data with salt using MD5 and several SHA algorithms. // // To run this sample, create a new Visual C# project using the Console // Application template and replace the contents of the Class1.cs file with // the code below. // // THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, // EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED // WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. // // Copyright (C) 2002 Obviex(TM). All rights reserved. // using System; using System.Text; using System.Security.Cryptography; /// <summary> /// This class generates and compares hashes using MD5, SHA1, SHA256, SHA384, /// and SHA512 hashing algorithms. Before computing a hash, it appends a /// randomly generated salt to the plain text, and stores this salt appended /// to the result. To verify another plain text value against the given hash, /// this class will retrieve the salt value from the hash string and use it /// when computing a new hash of the plain text. Appending a salt value to /// the hash may not be the most efficient approach, so when using hashes in /// a real-life application, you may choose to store them separately. You may /// also opt to keep results as byte arrays instead of converting them into /// base64-encoded strings. /// </summary> public class SimpleHash { /// <summary> /// Generates a hash for the given plain text value and returns a /// base64-encoded result. Before the hash is computed, a random salt /// is generated and appended to the plain text. This salt is stored at /// the end of the hash value, so it can be used later for hash /// verification. /// </summary> /// <param name="plainText"> /// Plaintext value to be hashed. The function does not check whether /// this parameter is null. /// </param> /// <param name="hashAlgorithm"> /// Name of the hash algorithm. Allowed values are: "MD5", "SHA1", /// "SHA256", "SHA384", and "SHA512" (if any other value is specified /// MD5 hashing algorithm will be used). This value is case-insensitive. /// </param> /// <param name="saltBytes"> /// Salt bytes. This parameter can be null, in which case a random salt /// value will be generated. /// </param> /// <returns> /// Hash value formatted as a base64-encoded string. /// </returns> public static string ComputeHash(string plainText, string hashAlgorithm, byte[] saltBytes) { // If salt is not specified, generate it on the fly. if (saltBytes == null) { // Define min and max salt sizes. int minSaltSize = 4; int maxSaltSize = 8; // Generate a random number for the size of the salt. Random random = new Random(); int saltSize = random.Next(minSaltSize, maxSaltSize); // Allocate a byte array, which will hold the salt. saltBytes = new byte[saltSize]; // Initialize a random number generator. RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider(); // Fill the salt with cryptographically strong byte values. rng.GetNonZeroBytes(saltBytes); } // Convert plain text into a byte array. byte[] plainTextBytes = Encoding.UTF8.GetBytes(plainText); // Allocate array, which will hold plain text and salt. byte[] plainTextWithSaltBytes = new byte[plainTextBytes.Length + saltBytes.Length]; // Copy plain text bytes into resulting array. for (int i=0; i < plainTextBytes.Length; i++) plainTextWithSaltBytes[i] = plainTextBytes[i]; // Append salt bytes to the resulting array. for (int i=0; i < saltBytes.Length; i++) plainTextWithSaltBytes[plainTextBytes.Length + i] = saltBytes[i]; // Because we support multiple hashing algorithms, we must define // hash object as a common (abstract) base class. We will specify the // actual hashing algorithm class later during object creation. HashAlgorithm hash; // Make sure hashing algorithm name is specified. if (hashAlgorithm == null) hashAlgorithm = ""; // Initialize appropriate hashing algorithm class. switch (hashAlgorithm.ToUpper()) { case "SHA1": hash = new SHA1Managed(); break; case "SHA256": hash = new SHA256Managed(); break; case "SHA384": hash = new SHA384Managed(); break; case "SHA512": hash = new SHA512Managed(); break; default: hash = new MD5CryptoServiceProvider(); break; } // Compute hash value of our plain text with appended salt. byte[] hashBytes = hash.ComputeHash(plainTextWithSaltBytes); // Create array which will hold hash and original salt bytes. byte[] hashWithSaltBytes = new byte[hashBytes.Length + saltBytes.Length]; // Copy hash bytes into resulting array. for (int i=0; i < hashBytes.Length; i++) hashWithSaltBytes[i] = hashBytes[i]; // Append salt bytes to the result. for (int i=0; i < saltBytes.Length; i++) hashWithSaltBytes[hashBytes.Length + i] = saltBytes[i]; // Convert result into a base64-encoded string. string hashValue = Convert.ToBase64String(hashWithSaltBytes); // Return the result. return hashValue; } /// <summary> /// Compares a hash of the specified plain text value to a given hash /// value. Plain text is hashed with the same salt value as the original /// hash. /// </summary> /// <param name="plainText"> /// Plain text to be verified against the specified hash. The function /// does not check whether this parameter is null. /// </param> /// <param name="hashAlgorithm"> /// Name of the hash algorithm. Allowed values are: "MD5", "SHA1", /// "SHA256", "SHA384", and "SHA512" (if any other value is specified, /// MD5 hashing algorithm will be used). This value is case-insensitive. /// </param> /// <param name="hashValue"> /// Base64-encoded hash value produced by ComputeHash function. This value /// includes the original salt appended to it. /// </param> /// <returns> /// If computed hash mathes the specified hash the function the return /// value is true; otherwise, the function returns false. /// </returns> public static bool VerifyHash(string plainText, string hashAlgorithm, string hashValue) { // Convert base64-encoded hash value into a byte array. byte[] hashWithSaltBytes = Convert.FromBase64String(hashValue); // We must know size of hash (without salt). int hashSizeInBits, hashSizeInBytes; // Make sure that hashing algorithm name is specified. if (hashAlgorithm == null) hashAlgorithm = ""; // Size of hash is based on the specified algorithm. switch (hashAlgorithm.ToUpper()) { case "SHA1": hashSizeInBits = 160; break; case "SHA256": hashSizeInBits = 256; break; case "SHA384": hashSizeInBits = 384; break; case "SHA512": hashSizeInBits = 512; break; default: // Must be MD5 hashSizeInBits = 128; break; } // Convert size of hash from bits to bytes. hashSizeInBytes = hashSizeInBits / 8; // Make sure that the specified hash value is long enough. if (hashWithSaltBytes.Length < hashSizeInBytes) return false; // Allocate array to hold original salt bytes retrieved from hash. byte[] saltBytes = new byte[hashWithSaltBytes.Length - hashSizeInBytes]; // Copy salt from the end of the hash to the new array. for (int i=0; i < saltBytes.Length; i++) saltBytes[i] = hashWithSaltBytes[hashSizeInBytes + i]; // Compute a new hash string. string expectedHashString = ComputeHash(plainText, hashAlgorithm, saltBytes); // If the computed hash matches the specified hash, // the plain text value must be correct. return (hashValue == expectedHashString); } } /// <summary> /// Illustrates the use of the SimpleHash class. /// </summary> public class SimpleHashTest { /// <summary> /// The main entry point for the application. /// </summary> [STAThread] static void Main(string[] args) { string password = "myP@5sw0rd"; // original password string wrongPassword = "password"; // wrong password string passwordHashMD5 = SimpleHash.ComputeHash(password, "MD5", null); string passwordHashSha1 = SimpleHash.ComputeHash(password, "SHA1", null); string passwordHashSha256 = SimpleHash.ComputeHash(password, "SHA256", null); string passwordHashSha384 = SimpleHash.ComputeHash(password, "SHA384", null); string passwordHashSha512 = SimpleHash.ComputeHash(password, "SHA512", null); Console.WriteLine("COMPUTING HASH VALUES\r\n"); Console.WriteLine("MD5 : {0}", passwordHashMD5); Console.WriteLine("SHA1 : {0}", passwordHashSha1); Console.WriteLine("SHA256: {0}", passwordHashSha256); Console.WriteLine("SHA384: {0}", passwordHashSha384); Console.WriteLine("SHA512: {0}", passwordHashSha512); Console.WriteLine(""); Console.WriteLine("COMPARING PASSWORD HASHES\r\n"); Console.WriteLine("MD5 (good): {0}", SimpleHash.VerifyHash( password, "MD5", passwordHashMD5).ToString()); Console.WriteLine("MD5 (bad) : {0}", SimpleHash.VerifyHash( wrongPassword, "MD5", passwordHashMD5).ToString()); Console.WriteLine("SHA1 (good): {0}", SimpleHash.VerifyHash( password, "SHA1", passwordHashSha1).ToString()); Console.WriteLine("SHA1 (bad) : {0}", SimpleHash.VerifyHash( wrongPassword, "SHA1", passwordHashSha1).ToString()); Console.WriteLine("SHA256 (good): {0}", SimpleHash.VerifyHash( password, "SHA256", passwordHashSha256).ToString()); Console.WriteLine("SHA256 (bad) : {0}", SimpleHash.VerifyHash( wrongPassword, "SHA256", passwordHashSha256).ToString()); Console.WriteLine("SHA384 (good): {0}", SimpleHash.VerifyHash( password, "SHA384", passwordHashSha384).ToString()); Console.WriteLine("SHA384 (bad) : {0}", SimpleHash.VerifyHash( wrongPassword, "SHA384", passwordHashSha384).ToString()); Console.WriteLine("SHA512 (good): {0}", SimpleHash.VerifyHash( password, "SHA512", passwordHashSha512).ToString()); Console.WriteLine("SHA512 (bad) : {0}", SimpleHash.VerifyHash( wrongPassword, "SHA512", passwordHashSha512).ToString()); } } // // END OF FILE ///////////////////////////////////////////////////////////////////////////////
http://www.obviex.com/samples/hash.aspx