A public salt will not make dictionary attacks harder when cracking a single password. As you've pointed out, the attacker has access to both the hashed password and the salt, so when running the dictionary attack, she can simply use the known salt when attempting to crack the password.
A public salt does two things: makes it more time-consuming to crack a large list of passwords, and makes it infeasible to use a rainbow table.
To understand the first one, imagine a single password file that contains hundreds of usernames and passwords. Without a salt, I could compute "md5(attempt[0])", and then scan through the file to see if that hash shows up anywhere. If salts are present, then I have to compute "md5(salt[a] . attempt[0])", compare against entry A, then "md5(salt[b] . attempt[0])", compare against entry B, etc. Now I have n
times as much work to do, where n
is the number of usernames and passwords contained in the file.
To understand the second one, you have to understand what a rainbow table is. A rainbow table is a large list of pre-computed hashes for commonly-used passwords. Imagine again the password file without salts. All I have to do is go through each line of the file, pull out the hashed password, and look it up in the rainbow table. I never have to compute a single hash. If the look-up is considerably faster than the hash function (which it probably is), this will considerably speed up cracking the file.
But if the password file is salted, then the rainbow table would have to contain "salt . password" pre-hashed. If the salt is sufficiently random, this is very unlikely. I'll probably have things like "hello" and "foobar" and "qwerty" in my list of commonly-used, pre-hashed passwords (the rainbow table), but I'm not going to have things like "jX95psDZhello" or "LPgB0sdgxfoobar" or "dZVUABJtqwerty" pre-computed. That would make the rainbow table prohibitively large.
So, the salt reduces the attacker back to one-computation-per-row-per-attempt, which, when coupled with a sufficiently long, sufficiently random password, is (generally speaking) uncrackable.
The point of rainbow tables is that they're created in advance and distributed en masse to save calculation time for others - it takes just as long to generate rainbow tables on the fly as it would to just crack the password+salt combination directly (since effectively what's being done when generating rainbow tables is pre-running the calculations for brute-forcing the hash), thus the argument that by knowing the salt someone could "generate a rainbow table" is spurious.
There's no real point in storing salts in a separate file as long as they're on a per-user basis - the point of the salt is simply to make it so that one rainbow table can't break every password in the DB.
Best Answer
I used reflector to take a look at those methods the .NET-Framework is using internal. Maybe there are public methods available for this but I did not find them - if you know how to query those internal methods as a user please left a comment! :)
Here is the simplified source-code without unnecessary conditions because I only want to encode the password as a SHA1-Hash: