Handling passwords safely in PHP
(Page 1 out of 2)Introduction
If you're ever going to create a script that involves users or passwords, which is very likely, you'll probably run across security issues with handling the passwords. You can't just store the passwords in clear text in your database, and great care must be used when managing the passwords (for example during login).
In this article I will show you everything that you have to think about when handling passwords in PHP, and how to solve some common problems.
Storing passwords
As I said just now, you can't store passwords in clear text, and they must be encrypted or hashed. The best way to store passwords is to hash them, and not encrypt them. The difference between hashing and encrypting is that with hashing it's completely impossible to get the original value, whereas with encryption it's possible to get the original value (by decrypting the encrypted string).
You should always hash important passwords and other sensitive data, because it means that no-one, not even a system administrator, can retrieve the original data. Of course, if you need to retrieve the original data, you'll have to use encryption, but for a password this isn't necessary.
The easiest way to hash a password is with the md5() function, which comes standard with PHP, and is very secure. It's used like this:
$secure_password = md5($password);
?>
Another standard hashing function that comes with PHP is the sha1() function, which does pretty much the same thing as the md5 function, except with a different algorithm and it's slightly more secure. It's used in the same manner:
$secure_password = sha1($password);
?>
Now it's already much safer to store passwords, but there is still one additional step you can take to really make it secure.
Instead of running the plain password through the hashing function, you should instead run the plain password with another small string, called a salt, through the hashing function, which makes the password really unique. Each user should have his own salt, and which means that each user has a completely unique hashed password, even if their plain password is the same. This also makes it harder to "crack" the hashed password, because it makes brute-force dictionary cracking almost impossible. Here's a simple example of what I mean:
// ... do some form handling, like validation, filtering, etc
$password = $_POST['password'];
// Generate a random salt
$salt = substr(md5(uniqid(rand(), true)), 0, 5);
// Hash password
$secure_password = md5($salt . md5($password));
// Store password AND hash in database
// ...
?>
As you can see in the above example, I used the uniqid() function to generate a random salt, and then used the salt to hash the password.
It's important to store the hash in the database as well, because you'll need it later when the user logs in, e.g.
// ... do some form handling, like validation, filtering, etc
$username = $_POST['username'];
$password = $_POST['password'];
// Get user from database
$user = getUser($username);
// Compare password
if ($user->password != md5($user->salt . md5($password))) {
die ('Wrong username or password!');
}
// ... user entered correct password, do something
?>
February 7th, 2006 at 2:45 am
Nice article. You can set up a SSL certificate without paying for it, but browsers will pop up a warning to users letting them know it is not signed by a trusted authority. Obviously not something you want to use on an e-commerce site, but it would be appropriate for a login to a personal system.
February 7th, 2006 at 8:23 pm
Wasn’t SHA1 cracked a while back?
http://www.schneier.com/blog/archives/2005/02/cryptanalysis_o.html
What about the crypt() function?
February 8th, 2006 at 9:44 pm
The advantage of crypting is that you’ll be able to decrypt the password in order to send it to the user in case he forget it. You can’t do this with hashing.
Mysql function ENCODE and DECODE are nice to do this way.
February 8th, 2006 at 11:13 pm
Bubba: SHA1 wasn’t really cracked, but some researchers found a quicker way to find collisions, but it’s still very secure, and safe to use.
Maxence: you’re right, that’s another advantage of encryption, but it’s still possible to use hashing. When a user forgots his password, the app has to generate a new one, and send it to the user. Not as nice maybe, but it is more secure.
February 9th, 2006 at 5:31 pm
I am new to all this
1. If there is a md5 hash function then is there an inverse of that. I mean if you can generate a hash of a string can you inversley get a string from a hash?
2. Also when the user enters the password and you post it with submit it is sent to your php script. has the password not just been sent to the server ready for for hashing? and if so could a hacker not retieve this as it is sent.
3. What is a secure connection (when IE asks if you want to go to a secure connection etc) and how can I create one.
February 10th, 2006 at 5:53 am
Where to get this md5 lib you showed us in the js?
February 10th, 2006 at 6:28 am
[…] PHPit - Totally PHP » Handling passwords safely in PHP (tags: php security) […]
February 10th, 2006 at 6:37 am
Ok I found one here http://pajhome.org.uk/crypt/md5/ put this with your web docs but you will need to replace the md5() function call for hex_md5().
February 10th, 2006 at 7:35 pm
The md5() lib can be found at: http://block111.servehttp.com/js/md5.js
February 13th, 2006 at 6:24 am
Guardando y manejando passwords en PHP
PHPit tiene un buen articulo con todos los detalles que hay que tener en cuenta para manejar y guardar passwords en PHP.
Tiene detalles interesantes como encriptar los passwords con un modificador unico por usuario, para que no se pueda hacer brute fo…
February 14th, 2006 at 11:21 pm
Using the $SERVER var is and isn’t a good thing, if the user changes there browser, char set or anything they will be logged out (or what ever you have set it to do) and they will have to re-login.
Saying that changing browser will make you have to re-login anyway, unless you have the cookie stored on both browsers and use them both to visit/test the site. Its still a good way to do things with sites that need it but if its only a simple CMS for any old site its not needed.
.Pat
February 25th, 2006 at 4:42 pm
Here is a random password generator tutorial
http://www.askbee.net/articles/php/Password_Generator/generate_random_password.html
March 7th, 2006 at 5:55 pm
What I’m not clear about when it comes to using the md5.js lib, is that doesn’t it have to reside on the user’s PC. If the user doesn’t have this lib on their PC, how is it going to work?
Hope someone can throw some light on that. Thanks!
March 7th, 2006 at 6:40 pm
Timmy,
1) You cannot get a string from a hash. This is the essence of hashing, it is one-way. To check if the user has entered their correct password, you re-hash the newly entered password and compare that to the old hash.
2&3) Yes, the password is unhashed between the user entering it in and the server script performing the hash function. The ’secure connection’ you refer to is when all the communication between the browser and the server is performed over the Secure Socket Layer (SSL). Secure pages will begin ‘https://’ in your address bar in your browser and the data is sent to the server encryted by a digitally signed certificate, an ‘SSL Certificate’, the details of which you can view by clicking on the little padlock icon in the bottom right of an IE browser when on a secure (https://) page. Using this method, you can ensure that the password gets to the server securely, without the risk of interception. Of course, somebody could always sniff the password from the keystrokes on the local computer, but then again, you can never absolutely guarantee security, you can only do your best to ensure it.
Regards,
PAUL.
March 12th, 2006 at 7:17 am
It’s a very usefull thing to me..
March 12th, 2006 at 10:23 am
It is a good idea to use salt, but it is quite useless though. If someone can get password hash and salt hash, brute-force is just as simple as without salt. One just adds the salt (which is known) and the result is same when brute-forcing passwords. Yes, the hash is different for each password, that is the only positive side of using salt - attacker has to brute-force every password instead of one if the users have same passwords (just as article said).
I chosed to hash password four times (could use salt, but chose not to), this way brute-force attack takes atleast four times longer :D :P One could hash e.g. N-times the password and the brute-forcing would be N-times longer (no, I have not took time, just using common sense for times).
For better (extreme) security: force users to have good passwords, keep salt physically different machine database (database in two different machines and php code in third), use N-times hashing.
_J_
March 24th, 2006 at 5:19 pm
Just a BIG doubt:
You wrote:
// Generate a random salt: $salt = substr(md5(uniqid(rand(), true)), 0, 5);
// Hash password: $secure_password = md5($salt . md5($password));
// Store password AND hash in database
So…
- You mean to store user,hashed password and salt, isn’t it? (Later, at login you used user’s salt so it has to be stored).
- random salts can be repeated. They are random, but like a dice, it can happen to be repeated. You force the unique condition on salt database’s field?
- Can username be used as salt? username is unique, so it will generate unique hashes.
- If it can… is there any safety problem? (like helping bruteforce if being known)
sorry about my poor English.
April 11th, 2006 at 2:09 am
I disagree that hashing your password clientside will add security. If a hacker manager to intercept the http-request, he may simply resend the same request from his computer and get access.
If however his software (and i can hardly believe this) does not support sending raw htt-requests or if he would like access using the browser, he may simply create a simple html form:
Username:
Password:
The only way which will realy work is to get an ssl cerificate.
---
The reason why you should double md5 andsalt your password, is to prevent the hacker from using a rainbow table (http://passcracking.com/) to guess the password when the hacker has got hold of the (single) md5 hash.
Let me explain: A certain word 'myS3cretP4zzword' will profide a certain hash 'ed64aa64dfac9a4d9eddcb50b521ccaf'. The rainbow table hold a large collection of strings with their hash equivilents. Since only the hashes are compared, the hacker may provide another string an different string than the password, generating the same hash, to login.
Should the hacker instead get hold of a double md5 hashed password (and the salts). He needs 2 steps: First he needs to find a 32byte value which generates the double hashed password and then he needs to run that trough a rainbow table.
The first step is (to my knowledge) impossible at this moment, but in the future people might create a rainbow table for 32byte values with their hashes. To make it even harder you may add a (5 character) salt. This mays that the hacker now has to come up with a 37 byte value which has to start with the given salt and will generate the double md5 hashed password.
---
To my opinion the best way to prevent session stealing is simply to store the ip address of the client:
$ip = getenv("REMOTE_ADDR");
If you host your website on a shared host, you might share the temp directory with other clients. If your host has been sloppy with the user privilges, another user on the server (the hacker) might be able to open and read you session files and even worst he might be able to write to you session files. The hacker could simply change the ip and steal the session.
Therefore it is whise to create a special (random) key for each session (no not the session id, because that is used for the filename) and store this in a cookie at the client. Now double md5 hash that key and save this in $_SESSION. Now the hacker needs both access to the clients PC to get the key and he needs to be able to write to the server, making it very hard to steal the session.
—
Remember, no system is 100% secure. The trick is to make your system more secure than your neighbor. So a thief will choose to break in to his system rather than yours. :)
Arnold Daniels
Helder Hosting
Senior developer
http://www.helderhosting.nl
April 11th, 2006 at 2:14 am
I didn’t think
April 12th, 2006 at 2:59 am
It’s me again :)
Having another look at my Authenticator class, i’ve noticed that I’ve done you a bit short telling that simply saving a random key in a cookie and save the hash of that key in the session would protect you from file-editing attacks. Since the hacker can simply set his own cookie and change the hash to match, this has isn’t making you app more secure.
Instead you want to prevent the hacker from changing the authorizing data in the sessionfile. By creating a hash combining user salt, user id, session id, ip and key, it is not possible for the hacker to change any of this data without knowing the user salt.
K, hope it helps.
Arnold
April 12th, 2006 at 3:01 am
$ip = getenv('REMOTE_ADDR');
$key = md5(microtime());
setcookie('session_key', $key);
$_SESSION['user_id'] = $user['id'];
$_SESSION['ip'] = $ip;
$_SESSION['session_pwd'] = md5($user['salt'] . $user['id'] . session_id() . $ip . $key);
April 12th, 2006 at 9:00 pm
Hey Arnold,
You’re pretty much right about most things, but I disagree about tying Sessions to IP address, because it doesn’t work in practice. Many big ISP’s (like AOL) actually share IP address among its thousands of users, and even worse, it’s possible for a user to get a different IP address between pageviews. If their session is tied to their IP address, they’d keep losing their session data.
That’s why I opted to use other data, like the user agent and language set in the browser, because that’s much less likely to change.
Also, hashing on the client side can work, if you don’t use a salt on the server side. When hashing the password on the client side, you should include the timestamp in the hash (i.e. md5(password+timestamp), and send the timestamp along. On the server side you check the hash, but you also check to make sure the timestamp is recent enough (.e.g older than 30 seconds, and its invalid). This means any one who intercepts the request can’t do much with the hash. Nor can they change the timestamp, as the hash won’t match then.
April 12th, 2006 at 10:54 pm
As long as a user doesn’t turn off (standby, sleepmode, etc) his computer, the computer will keep his IP lease. So browsing through the app shouldn’t give any problems. If the user goes for a cup of coffee and returns to his computer, he might need to re-enter his username and password when he return.
This is very rare though, since an ip-lease usualy last for 24 hours, meaning you have to turn your computer off for 24 hours to loose your ip and get a new one.
Saving the user agent, language, etc. does not add security, because there parameters are send by the client to the browser in the http-request. A hacker may simply copy the parameters (or even the whole http-header) of the client.
Please have a look at: http://web-sniffer.net/.
To the contrary, the ip is grapped from the socket connection, being the actual ip which is connected. Faking another ip is impossible… or atleast very hard.
If you realy want you could simply leave out the ip from the session_pwd without reducing a lot of security, since the cookie identifies the computer.
Arnold Daniels
http://www.helderhosting.nl
April 26th, 2006 at 1:50 am
Just a note, doing something like md5(md5($data)) does not make it any more secure.
In fact, it actually makes it less secure because there is more chance of a collision.
June 26th, 2006 at 8:19 pm
SH1 or MD5 can be decrypted , so be smart and dont use the raw info as the cookie info or whatever,
see here is the way you can decrypt the SH1 or MD5 crypted text,
http://www.md5encryption.com/?mod=decrypt
you been warned :)
June 29th, 2006 at 10:23 pm
Why not just add to the hash by corrupting it? For example, if you remove characters or add them to the hashed string, then you can’t decrypt them (unless you know what was lost or added).
July 11th, 2006 at 1:40 pm
I don’t think MD5 or SHA-1 can be this easily decrypted. You can read this blog about breaking SHA-1 http://www.schneier.com/blog/archives/2005/02/cryptanalysis_o.html .
What md5encryption.com does, IMHO, is building a dictionary online. When you hash a message, it would store it along its hash, either SHA-1 or MD5, then, when you “decrypt” it, it would simply retrieve the text.
Just try hashing something on your machine, and then “decrypting” it…
So be smart, and don’t fuel the dictionary…
July 17th, 2006 at 1:11 pm
Wow I really a complete beginner on all of this, can someone tell me what passwords we are talking about here?
Are they the passwords that users of a forum use to login to a forum or are they admin passwords?
I sorry if I’ve missed something here, as I’ve said I’m a complete beginner.
Thanks
Joe