Some Ideas for Securing Your Users’ Passwords in a Database
Posted: June 20th, 2007 | Author: Ben | Filed under: Web Development |I’m not an web-application developer and I certainly don’t have any experience at creating websites where security is mission critical. I work as a web developer in an advertising agency and every now and again need to create websites that have above-normal security for some of our clients. This article describes a method and I welcome any ideas to improve this method.
Overview
What if someone wanted to log into your website using someone else’s account? What if they managed to get a copy of your database?
If you have any information stored on a site that contains powerful functionality or information or that is valuable (or sensitive) to you or someone else, it’s worth making an extra effort to stop anyone else getting access. The first step is to make sure your users don’t use ridiculous passwords (because they probably will). How you can do that is up to you and isn’t within the scope of this article.
After the usual form submission and validation you’ll be at the point where you need to store that username and password in the database. You could do something like this:
"INSERT INTO users (username, passwd) VALUES ('$username', '$password')"
That would enter everything as-is and not help you at all if someone could see the contents of the ‘users’ table.
MD5 The Password
One of the first things I learnt about attempting to store information was the md5 (Message-Digest algorithm 5). This is a mathematical formula I don’t understand and never really want to. All I know is that it encrypts text strings with a 128-bit hash value and that’s good enough for me. On the sample table below, you’ll see how passwords are stored using the md5() function.
| user | password (md5) |
| jo | 5f4dcc3b5aa765d61d8327deb882cf99 |
| laura | 7fe4771c008a22eb763df47d19e2c6aa |
| pam | 5f4dcc3b5aa765d61d8327deb882cf99 |
| sean | f48861ca24e26a23a923ca68657079f4 |
To log in as user I would take the password they enter, pass that password through the md5 function and use the result to check for matches in the database.
The weakness here is that if someone really wanted in on your system they could use a word list of passwords against the table to crack the password. There’s actually online services (called rainbow tables) that offer to crack an md5 encrypted password in no time at all. These services will usually check the md5 hash against their databases of words and their md5 equivalents to find the password.
That’s a scary thought for anyone who felt comfortable with their md5 hashed passwords. There’s a method called salting that makes decryption far harder:
Salting
This is a really simple idea. You prefix passwords with a string constant made up of random characters (that you keep secret). So you’d set the constant in your php code like below:
define('mySalt', 'dvxcbTgh1245X');
You should also consider using extended characters to make your salt even more indecipherable.
And the passwords stored are prefixed with the constant:
$saltedPassword =md5(mySalt . $unencryptedPassword);
This will mean that the services to look-up passwords are going to have a much harder time, as they won’t recognise the md5 hashes they’re given:
| user | password (salted) |
| jo | 0b1cdf48291b451e8fbb33883f097471 |
| laura | 9a5648b4f0c7a1a24fba14dca2b05dda |
| pam | 0b1cdf48291b451e8fbb33883f097471 |
| sean | e445dd2da57303f20c31a4ca0c1c3cfc |
So we’ve done pretty well, although there’s a big hole in the plan. If our hacker was really determined, he’d notice that the passwords for Jo and Pam are the same. In practise, this happens a lot - especially on systems that are used by people working for the same organisation. The chances are that they’ll both have used their company name, street name, city or post code as a password. Finding two passwords that are the same will be a hint to me that the password will be a word common to both, so potentially guessable for any intrepid hacker.
Using the username
In order to make the identical plain-text passwords distinct from one-another when they’re encrypted we could consider making use of the username when storing the password.
We won’t use the username as the salt, as this could be a step backwards for us (think of someone using ‘green’ as a username and ‘fields’ as a password, which is probably not too hard to crack). So we’ll just add the username to the method:
$saltedPasswordWithUsername =md5(mySalt. $usernameLowerCase . $password);
I’ve converted the username to lowercase, as I don’t want my username to be case-sensitive, and different casing would affect the final md5 hash.
| user | password (salted and username added) |
| jo | ffeadd9ff3bf4e23584207bcf2c87c49 |
| laura | cc8d4ca7918eb1219252ee53647cc3f3 |
| pam | e2936a1ef84597accfc1704a25ae6bcc |
| sean | 18845b861a6c0e480a2be7db1d4c3341 |
You can see that Jo and Pam’s password hashes are now different, so there is no way to tell that unencrypted they are the same password.
Further Work
If you encrypt the passwords, you won’t be able to send password reminders to your users for those occasions when they’re forgotten. Instead you’ll need to write in functionality to reset passwords. Advocates of storing passwords as plain-text usually use the ’send me a password reminder’ functionality as their main defence in using this strategy. Sure, password reminders are easier to develop and the user probably prefers them to password resets, but having to apologise to your users for allowing a malicious hacker to gain access to their personal login information is a post I’d never want to have to write.
Possible Additions
You could possibly salt and md5 the username too, so to take away the one piece of information the hacker would use to get started. As mentioned above, it would be worth converting usernames to lowercase before they’re put through the md5 function as users aren’t used to case-sensitive usernames.













It’s a bit late I know, but I was going through your posts and found this one. Without being too much of a nitpick I’d like to remark upon the fact that md5 doesn’t really “encrypt” the data passed to it, but rather “hashes” it. The data is really being scrambled into an unrecognizable mess.
Also, iirc the md5 algorithm has been broken, which means that there indeed would be a way to derive the original data, from the output.
Another algorithm SHA1 (Secure Hash Algorithm) is probably your best bet, being both widespread and has, as of yet to the best of my knowledge, not been broken.
Other than that, good post
Cheers, Patrik