Password Security
Almost every website has some sort of members-only area. Anything from a profile system to something as simple as email subscriptions for articles. What every members area has in common is some way for a user to authenticate themselves. Most likely, this is through a username and a password. What I'm going to talk about today is the best practices for storing and handling passwords, since they should never be stored in plain text.
The first thing you may ask is why? Why shouldn't passwords be stored in plaintext? The answer is pretty simple: security. You should never store anything valuable in plaintext. While your database (or wherever else you use to store this kind of information) is usually secure from prying eyes, you want to secure the information in such a way that it is virtually useless to anyone who happens to see it. This can be anyone from another admin that is snooping through the database, to a cracker that somehow got access to the data. Many people use the same password at multiple sites, so a plaintext password in the wrong hands can really lead to some serious complications.
Storing Passwords
The password you store should be a hashed version of the password. This prevents just anyone from reading the password and is the simplest way to secure your system.
For those of you who do not know what a hash is, I'll take the time to try and explain it simply. A hash function takes some data (for example, a password) and converts it into some fixed-length series of bits usually represented in hexadecimal characters. The result of the function is always the same for the same data. It is irreversible. Unlike a cryptographic algorithm which is designed to be reversed with the correct key, a hash function is only designed to generate this "fingerprint". A hash function is also designed so that any two different pieces of data never produce the same result (this is not entirely true, but close enough).
In PHP there are two popular hash algorithms for you to use: md5() and sha1(). I would recommend the use of sha1() which is a bit more secure. Here are two examples of these functions and the result they produce:
Combine the attributes of a hash function (unique, irreversible, and nonsensical) and you get the perfect solution to secure passwords. Applying this concept to your applications is dreadfully simple. Where you would normally store a plaintext password, you now store the hashed password:
-
INSERT INTO users SET
-
");
Once you do this, there is one other aspect you need to take care of: logging in. Since the user would supply their password in plaintext, you need to hash their password before testing it against the one you have stored. There are usually two different methods programmers use to authenticate a user. The first is to use an SQL query with a WHERE clause so the passwords must match to return any row, the second is to just grab the user row and then compare the passwords in PHP. I will be using the second method.
Here's how your login code might look. Of course, $username and $input_password would filled with form values:
-
SELECT * FROM users
-
");
-
-
}
-
-
-
}
As you can see, it's just a matter of comparing the already hashed password from the database with the hashed version of the inputted password. If they don't match, then the user inputted the incorrect password.
Just this small alteration to your save and login code has increased your security substantially, it's is worth the ten minutes of work.
Other Concerns
We already know that a hash function cannot be reversed, but there are still ways to get the original string.
Brute Forcing
A brute for attack is where the attacker has the hash for a password and continually tries one string after the other until he finds one that matches. For example, he might try 'a', then 'b' ... 'ab', 'ac' etcetera for all permutations of the alphabet. This takes a long time for long strings, but a relatively short amount of time for shorter ones. The amount of time is also increased when you introduce more characters (for example if you let users input punctuation characters).
Dictionary Attacks
A dictionary attack is similar to the a brute force attack but instead of trying every single possible combination of characters, the attacker uses a dictionary and only tries the words in the dictionary. This attack can be very useful since the majority of non-tech savvy people will use passwords like 'pussycat99' and these are the kinds of strings good dictionaries will contain. Since the number of strings to test will be dramatically lower then the number a brute force will have to try, this can crack a password extremely quickly if it is not a secure password. Since most web applications are designed to be as user friendly as possible, it is not often that developers enforce good password policies on users so this problem is still something to worry about.
Rainbow Table Lookups
A rainbow table is a huge database of pre-computed hashes. This is where someone hashes every possible string (usually up to a certain length) and saves the hash. When an attacker wants a password, he just checks this massive database for the hash. If the hash is found, he can easily see the corresponding string that made it. These databases are usually really big, but it is not common for a rainbow table to contain records past 8 characters.
The Solution: Use A Salt
The most common solution to all of these problems is to use a salt. Note that a salt only adds security when talking about trying to reverse a hash back into the original string. It is not meant to protect against multiple login attempts from an attacker trying to gain access to a user account (though if the attacker already has the hash then it may be the ultimate goal). For those kinds of attacks, you should enforce an account lock-out feature. For example, only allow 3 consecutive incorrect login attempts before locking the account for an hour or emailing the administrator with details.
A salt is basically a random string that you append to the users password. The salt is stored separately and is used purely to prevent the aforementioned attacks. The salt itself doesn't need to be secret, just the fact that it is appended to the password thwarts most password attacks. To generate a salt, you simply get random characters. For example, here's an example generate_salt() function:
-
/**
-
* Generates a random salt from ASCII characters 33-126. These
-
* characters include all letters, numbers and punctuation.
-
*
-
* @param integer $len How long you want the salt to be
-
* @return string The generated salt
-
*/
-
function generate_salt($len = 8) {
-
-
$min = 33;
-
$max = 126;
-
-
$salt = '';
-
-
for($i = 0; $i <$len; $i++) {
-
}
-
-
return $salt;
-
}
Now when you save a password you append the salt and save both the salt and the salted password. Our save code above might be altered like this:
-
$salt = generate_salt();
-
-
INSERT INTO users SET
-
");
In doing so, a password mypass effectively becomes mypasscGdZPSEj. The random bit at the end means any attack is much much slower if not impossible. A brute force attack will become almost impossible with a long salt. A rainbow table lookup will most likely fail since there are certainly no databases that contain more then 10 character strings (most only go up to 8). A dictionary attack will be slowed down substantially when an attacker is trying to crack multiple passwords since each password will have a different salt and thus will need to be hashed separately.
Authentication With A Salt
If you are storing salted passwords, you once again have to change the way you authenticate. Just like how a plaintext password had to be hashed to be compared with the stored password, you need to hash and salt the inputted password before comparing. The above authentication code could be written like this:
-
SELECT * FROM users
-
");
-
-
}
-
-
-
}
Notice how on line 12 the salt is appended to $input_password.
What About 'Lost Password' Forms?
When you don't store the plaintext version of a password, it becomes impossible to send a user their password if they ever loose it. That's just the way things are. But that doesn't mean the user has no hope, you just have to take another approach. Instead of Lost Password forms, create Reset Password forms. Instead of sending the user an email with their password, send them a link with a special code that will reset the password and then send them the newly generated one. After they have logged in again with the new password they are free to go and change it to whatever they want.
Conclusion
I hope you've learned something today. I have seen many applications that still store passwords in plaintext, and it scares me a bit. I myself use a program on my Macintosh called info.xhead that stores all of my passwords for important things. Most of my passwords are 18 random characters, even I don't know what they are. But there are some common websites that I still use one password for (shame on me, I just can't be bothered to log into all of those old sites). It scares me because if an attacker gets hold of my one password, I sometimes wonder what other websites he will be able to gain access to. The Priate Bay, a torrent website, was recently hacked and the attacker gained all of the user information. Fortunately all of the passwords were hashed, but what if they weren't? I wonder how many users signed up with the same password as the password they used for their email account. Imagine how many compromised email accounts the attacker could have had access to. So do the world a favor: hash your passwords, and add in a dash of salt to make everything that much better.
Did you enjoy this post? Why not leave a comment below and continue the conversation, or subscribe to my feed and get articles like this delivered automatically each day to your feed reader.

No comments yet.
Leave a comment
Line and paragraph breaks automatic, e-mail address never displayed, HTML allowed:
<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>