The first tenet of password security is that one should NEVER save passwords in clear text! The proper way to save a password is by creating a cryptographic hash of a combination of a salt and the password. The use of a salt prevents an attacker who gains access to your password database from the use of rainbow tables to crack the passwords contained therein. Here is a static class which will hash, salt and compare passwords:
using System;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
namespace BasicLoginSystem
{
public static class PasswordHasher
{
/// <summary>
/// Creates a byte array containing the hashed combination of
/// a GUID-generated salt value prepended to the password, hashed
/// according to the provided algorithm.
/// </summary>
/// <param name="password">The plaintext password to be hashed</param>
/// <param name="hashAlg">The HashAlgorithm to use in hashing the data</param>
/// <returns>A byte array of the concatenated salt &
/// hashed salt/password combination</returns>
public static byte [] HashPassword(string password, HashAlgorithm hashAlg)
{
// Convert the password to a byte array
byte [] passwordAsByteArray = UTF8Encoding.UTF8.GetBytes(password);
// Generate a salt for the password
byte [] saltAsByteArray = Guid.NewGuid().ToByteArray();
// Hash the salt/password combination
byte[] hashedPassAndSalt = HashPassword(
saltAsByteArray, passwordAsByteArray, hashAlg);
// Prepend the salt to the hashed data
byte[] finalOutput = new byte[saltAsByteArray.Length + hashedPassAndSalt.Length];
Array.Copy(saltAsByteArray, finalOutput, saltAsByteArray.Length);
Array.Copy(hashedPassAndSalt, 0, finalOutput,
saltAsByteArray.Length, hashedPassAndSalt.Length);
return finalOutput;
}
/// <summary>
/// Prepends the provided salt to the provided password using the provided
/// HashAlgorithm
/// </summary>
/// <param name="salt">The salt to prepend</param>
/// <param name="password">The password</param>
/// <param name="hashAlg">The HashAlgorithm to use</param>
/// <returns>A byte array containing the hashed salt/password combination.</returns>
private static byte[] HashPassword(byte[] salt, byte[] password, HashAlgorithm hashAlg)
{
// Combine the salt and password into a single byte array
byte[] passAndSaltForHashing = new byte[salt.Length + password.Length];
Array.Copy(salt, passAndSaltForHashing, salt.Length);
Array.Copy(password, 0, passAndSaltForHashing,
salt.Length, password.Length);
// Hash the salt/password combination
return hashAlg.ComputeHash(passAndSaltForHashing);
}
/// <summary>
/// Compares a plaintext password provided by the user to the
/// password stored in a hashed salt/password byte array.
/// </summary>
/// <param name="password">The user-provided plaintext password for comparison</param>
/// <param name="storedPassAndSalt">The stored hashed salt/password combination</param>
/// <param name="hashAlg">The hash algorithm to use</param>
/// <returns>True if the password provided matches the stored pass, otherwise
/// false.</returns>
public static bool ComparePassword(string password,
byte[] storedPassAndSalt, HashAlgorithm hashAlg)
{
// Get salt from start of storedPassAndSalt
int hashSize = hashAlg.HashSize / 8;
// Deduce the size of the salt from the hash length
int saltSize = storedPassAndSalt.Length - hashSize;
// Extract salt from storedPassAndSalt
byte[] salt = new byte[storedPassAndSalt.Length - hashSize];
Array.Copy(storedPassAndSalt, salt, saltSize);
// Extract hash from storedPassAndSalt
byte[] hashedPasswordFromFile = new byte[storedPassAndSalt.Length - salt.Length];
Array.Copy(storedPassAndSalt, salt.Length, hashedPasswordFromFile, 0, hashSize);
// Using the salt extracted from the storeed password,
// hash the password we received from the user.
byte[] hashedPasswordFromUser = HashPassword(
salt, UTF8Encoding.UTF8.GetBytes(password), hashAlg);
// Compare the stored and provided hashes
return hashedPasswordFromFile.SequenceEqual(hashedPasswordFromUser);
}
/// <summary>
/// Create a human-readable hexadecimal string from the
/// byte array by walking the array and converting each byte
/// into a 2-digit hexadecimal value.
/// </summary>
/// <param name="data">The byte array to make human-readable</param>
/// <returns>The human-readable string</returns>
public static string CreateTextString(byte[] data)
{
// Create a human-readable hexadecimal string from the
// byte array by walking the array and converting each byte
// into a 2-digit hexadecimal value.
StringBuilder sb = new StringBuilder(data.Length * 2);
for (int i = 0; i < data.Length; ++i)
{
sb.AppendFormat("{0:x2}", data[i]);
}
return sb.ToString();
}
/// <summary>
/// Transform the provided human-readable hexadecimal string to
/// an array of bytes.
/// </summary>
/// <param name="data">The string to transform</param>
/// <returns>The byte array representation of the hexadecimal string</returns>
public static byte[] CreateByteArray(string data)
{
// Since each byte is represented by a 2-digit hex number,
// we know that the length of the resulting byte array is
// half the length of the passed-in data.
byte[] binData = new byte[data.Length / 2];
for (int i = 0; i < data.Length; i += 2)
{
binData[i / 2] = Convert.ToByte(data.Substring(i, 2), 16);
}
return binData;
}
}
}
The publicly-accessible HashPassword function will take a given password and generate a salt based on a generated GUID, then hash the two and prepend the salt to the result, taking advantage here of the fact that any given hash function returns a hash which is of a known size.
When we use the ComparePassword function we can extract the salt from the beginning of the data -- based on the known size of the hashed data -- and reverse the process performed in the aforementioned HashPassword function. That is, we
- Extract the salt and the hashed salt + password combination
- Hash the salt and the provided password using the provided algorithm
- Compare this calculation with what was saved in the file/database
If the data compares favorably, then we know we have a successful login.
The next part of the tutorial will create the login system interface, through which we can save the login data, starting with the simplest: a text file.




MultiQuote



|