This is really simple, but don’t feel bad if you didn’t know about this. No one tells me nothin’ either.
The other day while reading through a blog, I got schooled without going to class. I had no idea that passwords should be one way only, as in you shouldn’t be able to retrieve a forgotten password, only reset it. Well shoot, I missed that one. So they start talking about hashing the password and saving that. Good thing I had built something to hash request items in a url to help stop people with screwing with a site url. Well ok, I didn’t totally build it. I got the idea from somewhere else. But I f-ing integrated it so don’t look at me like that.
Basically you take a password, add a salt (fancy name for a predetermined string or number) to the password, and get the hash value for that.
Why getting the same hash everytime sucks
Well the problem with hashing, or really the reason why we are doing this, is that the word “pass” creates the same hash value every time. Now if some sort of mean person figures out the hash for “pass” (That isn’t too hard since most people use some sort of standard like the .net MD5CryptoServiceProvider), he/she could search for a slew of typical password words. See the problem? Now enters the salt. The salt is some word that you come up with to add anywhere you want. This makes it a little difficult to figure out since the word “passSalt” is no longer like “pass”. one could try every word known and still fail because said person doesn’t know to add the word “Salt”.
Why getting the same hash everytime is great:
Right now you might be wonder what the point of having a password saved that can’t be unhashed to check against. Easy, you don’t have to unhash. Since the hash for “passSalt” will always be the same, the password in the database will always match the entered password + the salt. So basically the user enters the password, the system adds the salt to the password, the system then gets the hash, and finally checks that against the database record. Fun huh?
Now for the code:
public class HashString
{
private const String DEFAULT_SALT = "8745";
public static String CreateHash(String originalValue, String salt)
{
Byte[] computedHash;
StringBuilder hashedValues;
StringBuilder hashString;
//Take the string and append your "secret" string at the end.
hashString = new StringBuilder(originalValue);
hashString.Append(salt);
//Get the hash for the new word.
computedHash = new MD5CryptoServiceProvider().ComputeHash(Encoding.UTF8.GetBytes(hashString.ToString()));
hashedValues = new StringBuilder();
//Go through computedHash and create a string from it.
computedHash.ToList().ForEach(currentItem =>
ashedValues.Append(currentItem.ToString("x2")));
return hashedValues.ToString();
}
//Not needed overload, just have it here for ease of use
public static String CreateHash(String originalValue)
{
return CreateHash(originalValue, DEFAULT_SALT);
}
}
And I even have a test for you… if you have VS Professional or higher. If not, no big deal. Just remove the asserts, attributes, and step through.
[TestClass]
public class HashStringTest
{
private class UserTable
{
private const String SALT_VALUE = "SomeValue";
private List table;
public UserTable()
{
table = new List();
}
public void AddUser(String password, String userName)
{
table.Add(new UserRow(){Password = HashString.CreateHash(password, SALT_VALUE), UserName = userName});
}
public Boolean UserExists(String password, String userName)
{
String hashedPassword;
hashedPassword = HashString.CreateHash(password, SALT_VALUE);
var query = from user in table
where user.Password == hashedPassword && user.UserName == userName
select user;
return query.ToList().Count == 1;
}
private class UserRow
{
public String Password { get; set; }
public String UserName { get; set; }
}
}
[TestMethod]
public void CreateHash_PasswordMatchPass()
{
UserTable table;
String userName;
String goodPassword;
String failedPassword;
userName = "SomeUser";
goodPassword = "goodPassword";
table = new UserTable();
table.AddUser(goodPassword, userName);
Assert.IsTrue(table.UserExists(goodPassword, userName));
failedPassword = "failedPassword";
Assert.IsFalse(table.UserExists(userName, failedPassword));
}
}
And last, the namespaces:
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;
using System.Text;
using System.Security.Cryptography;
using Microsoft.VisualStudio.TestTools.UnitTesting;
For the record, I hate the word salt in this use.