One of the more confusing things I’ve encountered in web development is creating a secure login system. I had trouble finding a clearly laid out guide that explained exactly what steps to take. So, now that I’m fairly familiar with the process, I’ve decided to write this guide as a practical series of steps using PHP, MySQL, and a bit of Javascript. Before you get started, you should realize that these steps are sufficient for anything as sensative as up to (and maybe including) a webmail system. Anything involving monetary transactions MUST take further steps which go beyond the scope of this article.
We need to define a few terms first:
Salt – A randomly generated string of at least 70 characters, unique for every user. This is stored in your database.
Pepper – Generated the same way as the salt, but only once, same for every user and is stored in a php file.
Hash – Any of several algorithms that encrypts data in such a way that it is almost impossible to reverse back to the original string. It is generally recommended that you use SHA256 or, in a few years or so, something better.
Username – A unique identifier for every user. For the purposes of this guide, we will assume that you force it to contain only Aa-Zz, 0-9 and underscores.
Password – A bit of information that may or may not be unique. It may contain any characters.
Unless mentioned otherwise, everything is done server side.
Now, the procedure I use for registering a new user:
- (client side) Concatenate the username and password together and hash them. Send this hash to the server.
- Generate a new salt, concatenate it with the result from 1 and the pepper. Hash this.
- Store the result from 2 and the salt in the database
- (client side) Concatenate the username and password together and hash them. Send this hash to the server.
- Retrieve old salt from database, concatenate it with the result from 1 and the pepper. Hash this.
- If the result from 2 matches with the old stored hash, then the user is valid, procede to 4. Otherwise, return an error message.
- Generate a new salt, concatenate it with the result from 1 and the pepper. Hash this.
- Store the result from 4 and the new salt in the database, overwriting the old values.
Next, I always concatenate the username and password together because this ensures beyond any shadow of a doubt that the thing that is hashed is absolutely unique. Yeah, the random salt does this too, but… random is random and I like to make sure. As for the length of the salt (and pepper), having it longer is better. Basically, it forces the SHA256 algorithm to do more work which increases entropy and such.
Finally, steps 4 and 5 of the login were the steps which took me the most time to understand. The basic idea is that a salt is completely useless unless you can remember it. So, you store it alongside your hashed password (or, “keyphrase”… i.e. username + password + salt + pepper) and use it once before you ditch it and create a new one. This keeps your user table constantly changing, which is a great defence against any would be hacker.
Resources and code examples:
A great, concise article detailing why many of these steps are needed, written by devnetwork’s own Mordred.
Javascript SHA256 implementation
Hash(), usage: hash(“sha256”, “whatNeedsHashing”);
Creating a random string consisting of the characters 0-9, Aa-Zz, :, ;, <, =, >, ?, @, [, \, ], ^, _, `, {, |, }, or ~.
Code: Select all
for($i=0; $i<$length; $i++) {
$salt .= chr((mt_rand() % 78) + 48);
}Code: Select all
If(preg_match(“#[^0-9a-z_]#i”, $username)
return $badusername;Final Thoughts:
When concatenating strings together, don't do it in the order I have listed above. Be creative! Maybe split your salt up in a predetermined manner while mixing it in with your username/password. The only reason to keep things simple in a thing like this is sheer laziness.
If you would like to see the entirety of one of my current login scripts, I have it available upon request. Just shoot me a pm.
These steps are by no means an exhaustive method to ensuring your user's security. If you really want it "perfect", you need to use an https connection as well as probably doing some sort of handshake method (as helpfully detailed by Maugrim).
------------------------------------------------------------
Please help me improve this article! Are there any glaring errors (especially factual ones)? In particular, is hashing the password client side truly helpful, or am I just pulling stuff out of thin air? Finally, what would you feel comfortable using such a system for? Also, hello everyone... I haven't posted anything in a long time, but I've been lurking!