One great technique out there is what is known as the iterative hash. This is simply taking the hash of some data and hashing the resulting hash and then hashing that hash again.

pseudo-code example of this:
string hash = sha256('data' + 'salt');
hash = sha256(hash);
hash = sha256(hash);
...etc.

By doing this many times (1,000-10,000 times maybe) makes the hash incredibly hard to crack. If an attacker steals your hash and finally cracks it all they have is a new hash to crack. Then this process has to be repeated again and again and again. Even if it only takes 2 days to crack a hash it would take 2,000-20,000 days now to crack that hash. I will take that any day.

Now their is a performance hit to this of course. I did a bit of benchmarking a while back and found that 1,000 iterations took 0.36 seconds and by jumping up to 10,000 seconds I actually got better performance at 0.19 seconds (original post on this here). So tune how many iterations to what your system will handle (and someone tell me why I get the unexpected result).

Basic Implementation using SHA512.

byte[] data = Encoding.UTF8.GetBytes(dataToHash.Trim() + salt.Trim());
HashAlgorithm algorithm = new SHA512Managed();
for (int i = 0; i < iterations; i++)
{
     data = algorithm.ComputeHash(data);
}

Of course you can use whichever hashing algorithm you want here. If you wanted to get crazy you could mix and match your algorithms to prevent any future weakness in an algorithm from making your hashes vulnerable. While it is nice to have that protection it adds many more failure points which makes it hard to debug issues.

There is also a builtin class in the .NET framework that can be used to generate iterative hashes. It is called Rfc2898DeriveBytes (catchy hey!). Here is a quick code sample of it.

Rfc2898DeriveBytes db;
String data = "HashMe";
int iterations = 1000;
byte[] salt;

if (_salt.Length == 0)
    db = new Rfc2898DeriveBytes(data, _saltSize, iterations);
else
    db = new Rfc2898DeriveBytes(data, _salt, iterations);

byte[] result = db.GetBytes(200);

One of the nice features is that the Rfc2898DeriveBytes class has an overload that we could just pass in the salt size and it will generate the salt for us. Alternatively we can supply our own salt. The hardcoded 200 in the example is how many characters we want our hash to be. I am unsure of what is going on internally inside of this class still as far as algorithms to do all of this work. I have also not been able to find out how hard it is to crack the generated hash either so I have leaned towards using the SHA iterative hasher from before as I have more data on it.