The best way to defend yourself is to know how to attack yourself so here are some of the scenarios a password system may be attacked with
Brute forcing the application
Here an attacker is attacking the website/winform by throwing a dictionary/random strings at it. In this case the application still loads the salt from the database and does all the work to verify the password. So salting is transparent to the attacker and has zero influence on this type of attack. The best mitigation techniques are account lockout policies (3 failed logins and the account gets locked), forcing strong passwords, and password change policies.
Stolen Database + Brute Force
Here an attacker has stolen the database and is trying to hack the hashes directly. In this case the account lockout policy will not factor in as that is controlled by the application which we are no longer going through. Strong passwords help here as they are harder to brute force. Also password change can really kick in as if it takes 45 days to crack the targeted hash then by the time I have cracked it the user has changed their password on the live database. The biggest advantage here is salting. Now instead of having to crack a string of 'MyPass' the string is actually 'MyPass#$^q∩gwjoεai←cyuw3b5asdφ♂' as it includes the hash.
This attack actually breaks down into two potential techniques. The first is to generate random strings and check it against every hash in the database until we get a match. When we do get a match it will be something like 'MyPass#$^q∩gwjoεai←cyuw3b5asdφ♂' so we can easily tell which part is the password and which part is the hash. We then go to the production system and enter in 'MyPass' and the system performs the login steps and we are logged in.
The second technique is usually used when going after a specific hash instead of all of the hashes. In this attack we take our dictionary/random string + the salt we already know from the database and run it through the hashing algorithm. This is usually a lot faster as people use weak passwords and we already know the salt portion of the string. So Instead of having to guess 'MyPass#$^q∩gwjoεai←cyuw3b5asdφ♂"' as we did with the previous method we already know that part of the string will be '#$^q∩gwjoεai←cyuw3b5asdφ♂' so we just brute force 'random' + '#$^q∩gwjoεai←cyuw3b5asdφ♂'.
So by salting we have done two very important things. We have made strings to crack very long and added in random and non alpha numeric characters making the hash way harder to crack so instead of taking minutes/days/hours to crack a simple password it is now weeks/months to crack it. We have also made it incredibly hard to generate one hash and check it against all passwords. I will illustrate that with an example:
Table Without Salting:
| Uid | Hash |
| 1 | AS24673SAGA |
| 2 | JASdf890246 |
| 3 | 5734ASDFga89 |
| 4 | #&&asdgu3 |
| 5 | %$#sadfFH% |
Because there are no salts in here the hashes are all just the result of hashalgorithm.CreateHash('plaintextword'). So if we want to brute force all we do is hashalgorithm.CreateHash('randomword') and then take the output of that and check it against ALL rows of the database to see if we get a match. So by generating one hash we can check 5 hashes to see if we get a match.
Table With Salting:
| Uid | Hash | Salt |
| 1 | SGJGHD6w34* | @$ASDG456 |
| 2 | GSJS66r657 | ASDY^#$QM |
| 3 | ^*DFHhfsdfh | ADtjws& |
| 4 | JKFD^4uwsry | AH#$#$^&Y# |
| 5 | &$DN#TMEB | ^$*#GJ% |
Here all the hashes were created with hashalgorithm.CreateHash('plaintextword' + 'salt'). If we want to brute force now we either have to do hashalgorithm.CreateHash('randomword' + 'randomGuessAtSalt') and check that against the whole database (i.e. totally ignoring we have a salt column here). The second attack is to take hashalgorithm.CreateHash('randomWord' + 'knownSaltFromDatabase') but now we can only check against one row at a time instead of all of them.
Really Bad Math
The last time I did math on permutations and calculations was about 10 years ago. Please email me if you find any inconsistencies in my math and I will correct it.
We will say for our sample that we are using an alpha numeric password (so we 62 characters) choose from and we will say a password is 6-10 characters long
The maximum number of computations to get a 6 character hash then is: 62^6 = 56,800,235,584 computations
The maximum number of computations to get a 10 character hash then is: 62^10 = 839,299,365,868,340,224 computations
For a table without salting we get the bonus of calculating one hash and checking it against all the records in our table so I came up with this formula:
(Computations * TimePerComputation) / Records in database = Time Required to crack the first hash
Assuming it takes 0.0000306 seconds to generate one check and we are dealing with a six character password:
Time to crack ONE un-salted password
| # of Records | Time Required (seconds) | Time Required (Hours) |
| 1 | 17,380,807 | 4,828.00 |
| 10 | 1,738,081 | 482.80 |
| 100 | 173,808 | 48.28 |
| 1000 | 17,381 | 4.8 |
Time to crack ONE salted password (without factoring in the added string length/complexity of the salt itself)
| # of Records | Time Required (seconds) |
| 1 | 17,380,807 |
| 10 | 17,380,807 |
| 100 | 17,380,807 |
| 1000 | 17,380,807 |
So now if we add a 128 bit salt which would add 3.4028236692093846346337460743177e+38 permutations of complexity ( I derived this via 2^128 i.e. a bit can be 0 or one and that is repeated 128 times)
64^6 + 3.4028236692093846346337460743177e+38 = 3.4028236692093846346337460750049e+38 calculations
3.4028236692093846346337460750049e+38 * 0.0000306 seconds/calculation = 1.0412640427780716981979262989515e+34 seconds
1.0412640427780716981979262989515e+34 seconds = 3.29963712 × 10^26 years
The Rainbow Table
This is a relatively new attack and quite ingenious and as hard drive sizes and processor speeds have increased it has become quite practical. The basic idea is this:
1. Generate all the permutations of passwords and hash them.
2. Store the hashes in a file along with the word used to create them.
3. When cracking a hash just search the database and out comes the word used to create that hash
So now we have a lot of initial time invested to create the table and very little to look it up now. I have used this technique to crack my own passwords (which met the windows complexity rules too) in 10 minutes. Password change policies would not have helped me there.
But don't think the sky is falling and passwords are dead (well they are but that is a whole other series of posts on that subject). By having a long password (or passphrase) makes this harder to perform as now you need a table computed for password of 6-100 characters instead of 6-10 characters. This would result in a table of well over 100GB and would be even slower to search. Salting also adds to the length and complexity of a password (because a good salt will be long and have non alpha-numeric characters in it) which makes for a huge and slow rainbow table.
Conclusion
These method is used by many operating systems and software applications to store passwords. I would not recommend another way to do it besides a hash. Hashing on its own helps and adding a salt helps slow down every attack form. The fact that we have to store salts is the big downside here in that if an attacker got both the hash and a salt it could reduce the time to break a hash (yes I will have a post about securing hashes coming soon). The advantages of hashed passwords far outweigh the disadvantages in my mind.