Let's first consider the example where we simply store users' passwords in plaintext (that is, if a user's password is "abc123" we store "abc123" in the database). Consider the following set of users.
| User | Password | Password data stored in database |
|---|---|---|
| User1 | abc | abc |
| User2 | abc | abc |
| User3 | abd | abd |
| User4 | def | def |
(Aside: encrypting the database with a secret key could be seen as a solution; however, the majority of web attacks are done through the likes of SQL injection vulnerabilities, which execute at a level at which the database can be read in a decrypted form.)
A good first step is to apply a public, well-tested hash function to the passwords in the database, and store the password hashes rather than the passwords themselves. A hash function is a one-way function; that is, is it easy to perform one way (i.e. turning the password into the hash), but hard or impossible to do the reverse (i.e. turning the hash into the password). Since hashing algorithms map to a fixed-length string, there are guaranteed to be hash collisions and so it is often impossible to know the original text given the hash. For this example I am going to use the SHA-1 algorithm, which is conveniently built into many web programming languages including PHP. Our database would now look as follows.
| User | Password | Password data stored in database |
|---|---|---|
| User1 | abc | a9993e364706816aba3e25717850c26c9cd0d89d |
| User2 | abc | a9993e364706816aba3e25717850c26c9cd0d89d |
| User3 | abd | cb4cc28df0fdbe0ecf9d9662e294b118092a5735 |
| User4 | def | 589c22335a381f122d129225f5c0ba3056ed5811 |
- Anyone looking at the database would know that User1 and User2 have the same password
- Databases using popular hashing algorithms are still vulnerable to rainbow tables. These are huge tables holding the result of hashing every possible string, often up to a certain number of characters. For example, the rainbow table available here for SHA1 hashes of lowercase letters, numbers and spaces up to 9 characters is already 52GB! Anyone with a hash could run it through such a rainbow table and come up with possible sources for that hash.
A way to protect against rainbow table attacks is to ensure that the number of characters in the plaintexts are not included in the rainbow table's domain. For example, in the example above, the 52GB rainbow table only worked when the passwords were 9 characters or less - so anything above 9 characters would be invulnerable to this current rainbow table. And since it's extremely low overhead to hash something as short as a string of text, we might as well add, say, 10 characters to each password. (In practice you would want to do more than this, but I want to fit everything on the page!) A common string of characters that we concatenate with every password is called a salt. Let's take our salt as "abcdefghij". Then, for example, the first entry in the database will be the result of applying the SHA-1 function to "abcabcdefghij".
| User | Password | Salt | Password data stored in database |
|---|---|---|---|
| User1 | abc | abcdefghij | 0143aa54b192a3db577861c54d60486920634b28 |
| User2 | abc | abcdefghij | 0143aa54b192a3db577861c54d60486920634b28 |
| User3 | abd | abcdefghij | 03c313d4f74ab4d53a7312f42ca1287938169aed |
| User4 | def | abcdefghij | 5d4b46aa07beb42a8057f1913e70e8d9e53bdbc7 |
| User | Password | Salt | Password data stored in database |
|---|---|---|---|
| User1 | abc | abcdefghij | 1cf0c7f82fa8dbbb3e68b96130d33b8defaf1791 |
| User2 | abc | abcdefghij | 1cf0c7f82fa8dbbb3e68b96130d33b8defaf1791 |
| User3 | abd | abcdefghij | 63480fa579f5a067db0254771ba13bdc6534503b |
| User4 | def | abcdefghij | 9a19325b82d0a8d6a3748c71d6e4d767e4a3fb14 |
| User | Password | Salt | Pepper | Password data stored in database |
|---|---|---|---|---|
| User1 | abc | abcdefghij | qwertyuiop | e27cb9a5f3973a5d173f6d5da95565ca668f3fea |
| User2 | abc | abcdefghij | asdfghjklp | b0c8d626936e30a7b7561be5ad4384e43454ab04 |
| User3 | abd | abcdefghij | zxcvbnmkop | 7ed2c4d59f006c5d321f653cb1a98ec8cf8e6a8d |
| User4 | def | abcdefghij | 1q2w3e4r5t | 265f5ac148f1242c8af4ee2e70dcbe077519a3f7 |
There are some additional measures we could take to increase the security of the system as a whole.
- Hash the user's password client-side using JavaScript or similar so that the server never "sees" the original password. This will prevent a passive man in the middle attack. (While we're at it, we may as well salt the hash.)
- Use a session-encryption technology like https. Such a technique is required to prevent active MITM attacks, as additional layers of JavaScript will only provide obscurity rather than security if an attacker can change a page's or script's content in transit to the user.

The site owner would take the data provided by the user, run it through this process, and compare the result with the data stored in the database. If they match, the password the user originally entered must have been correct. All this can be done by the owner with absolutely minimal computational overhead. Are you paying attention, Sony?
We pepper the passwords because otherwise a single rainbow table would be enough to attack all the passwords in the database. It would still have to be generated with the salt in mind (so the vanilla rainbow tables wouldn't be effective) but theoretically, if a 10-character rainbow table was generated and all passwords stored in the database were less than 10 characters, that one table could be used to crack all the passwords.
The need to salt everything if passwords are peppered (i.e. already individually salted) is more subtle. While it may not be clear in the post above, we ALWAYS want to store the salt outside of the database - most often this means we hard-code the salt in server-side code. The idea here is that the salt will not be compromised if the contents of the database are stolen - the attacker will want to construct a rainbow table, but will not know what salt was used! (In fact, he shouldn't know that extra salt exists at all, giving you a strong layer of security through obscurity.)