ASPit - Totally ASP JSit - Totally JavaScript
Search PHPit

Use this textbox to search for articles on PHPit. Seperate keywords with a space.

Advertisements

Handling passwords safely in PHP

(Page 2 out of 2)

Logging in or Registering

When a user logs in or registers a new account, he/she probably has to enter a password, and this password will be sent to your server, but have you ever thought about how the password is sent? It's sent as plain text, and ANYONE on the same network and armed with a packet logger can see everything that's sent, including any passwords. Obviously, this is a huge problem, yet many developers fail to solve it.

There are two (and possibly more) solutions. The easiest solution is to simply use an SSL cert, and run your PHP application on a secure connection (https), but the downside of this is that you will have to pay for the SSL cert, your web host might not support it, and it costs more server resources, because all incoming traffic has to be decrypted. On the other hand, it is very easy to implement and extremely secure.

But we can also create our own 'home-made' solution which adds a bit of protection as well, and it only requires some JavaScript. What we're going to do is hash the password on the client-side, using JS and a md5 lib, and then compare it on the server-side, with PHP.

The login page is really simple, and only uses some basic HTML and JavaScript:


       
                </span>Login<span class="sc2"><span class="kw2">

                type="text/javascript" src="md5.js">

                type="text/javascript">
                        function hash_password() {
                                // Get password
                                var password = document.getElementById('password');

                                // Hash password
                                var secure_password = md5(password.value);

                                // Save hashed password
                                document.getElementById('secure_password').value = secure_password;

                                // Remove plain password
                                password.value = '';
                        }
               
       

       

                action="login.php" method="POST" onsubmit="hash_password();">
                        Username: type="text" name="username" />
                        Password: type="text" name="password" id="password" />

                        type="submit" name="submit" value="Login" />

                        type="hidden" name="secure_password" value="" id="secure_password" />

               

       

The above page uses a md5 library to hash the plain password just before it gets sent to the server, and makes sure the plain password never gets sent by setting the value of the plain password field to nothing.

The PHP page that handles the login form is equally simple:

// Both vars there?
if (!isset($_POST['username']) OR !isset($_POST['secure_password'])) {
        die ('Wrong username or password');
}

$username = $_POST['username'];
$secure_password = $_POST['secure_password'];

// Get user from database
$user = getUser($username);

// Compare password
if ($user->password != md5($user->salt . $secure_password)) {
        die ('Wrong username or password!');
}

// ... user entered correct password, do something

?>

The above code immediately uses the secure password with the salt, and compares it to the stored password.

We've solved part one of the problem, and the password is no longer sent as clear text, but it's still possible for "hackers" to login in as a user, because they can still steal the hashed password, and use that to login (by creating their own login page that immediately sends the hashed password).

To solve that you can try several things, like making the password time based. For example, when hashing the password on the client-side, you add the time as a salt, and then compare the result and the time, to make sure the password that was sent isn't any older than 30 seconds, on the server-side, but this only works when you're not using user salts (which you should really).

If you want to work around this problem, you can either make the login two steps (i.e. enter username first, then get salt, and then enter password) or you could use Ajax to retrieve the salt and hash the password.

When it comes down to it, you can't really protect the password, without going through lots of hoops, or by using a SSL cert, which is ultimately the best solution.

Let's have a look at another aspect of passwords: storing them in cookies or sessions.

Storing passwords in cookies or sessions

In practice you should never store passwords in cookies or sessions, but if you're going to create a "Remember Me" function you'll probably have to.

When you're using sessions, you won't have to store the password at all, and instead you should store a simple key the first time a user logs in, which tells you whether the user is logged in or not, e.g.

// ... login procedure, and everything is okay

session_start();
session_regenerate_id();

$_SESSION['loggedin'] = true;

?>

In every subsequent page you can now check the $_SESSION['loggedin'] variable whether the user is logged in, and the password never reached the client-side (the user's computer).

But for a 'Remember me' function this won't work, and you'll have to use cookies for that, which means you have to protect it somehow.

The easiest way to do this is to hash the password before writing the cookie, which means the password isn't stored in plain text. But what if the cookie gets stolen by a "hacker", and is then used? It would allow the hacker to login as the user, so you must try to prevent this.

The best way to prevent this is to make the password identity password. For example, when hashing the password, also include the following three things (all retrievable through the $_SERVER variable):

- The user agent of the browser
- The language of the user
- The accepted charset

Now when a hacker steals the cookies, there's a big chance it won't work, because the hacker might have different settings (a different user, language or charset). Of course, this is far from fool-proof, and it's easily fooled if the hacker knows what settings the user had, but it's better than nothing.

Conclusion

In this article I have talked about three different aspects of handling passwords: storing them, sending them, and remembering them. In each instance you have to carefully think about how to secure the passwords, and prevent anyone else from stealing and abusing them.

In many cases it's security versus ease of use. For example, if you make passwords identity based in cookies, and a user changes something (for example his language) his cookies would be invalid, and he'd have to login in. This is an inconvenience, but is it worth the extra security? That's the question you'll have to ask yourself every time you implement a new security measure.

If you have any comments or questions, leave them below or join us at PHPit Forums.

« Previous: Storing Passwords



28 Responses to “Handling passwords safely in PHP”

  1. Seattle Web Says:

    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.

  2. Bubba Gump Says:

    Wasn’t SHA1 cracked a while back?

    http://www.schneier.com/blog/archives/2005/02/cryptanalysis_o.html

    What about the crypt() function?

  3. Maxence Delannoy Says:

    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.

  4. Dennis Pallett Says:

    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.

  5. Timmy Says:

    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.

  6. turtle Says:

    Where to get this md5 lib you showed us in the js?

  7. Gilles Dubois Bookmarks » links for 2006-02-10 Says:

    […] PHPit - Totally PHP » Handling passwords safely in PHP (tags: php security)   […]

  8. turtle Says:

    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().

  9. Dennis Pallett Says:

    The md5() lib can be found at: http://block111.servehttp.com/js/md5.js

  10. kbglob Says:

    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…

  11. Patrick Ohearn Says:

    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

  12. Cmircea Says:

    Here is a random password generator tutorial
    http://www.askbee.net/articles/php/Password_Generator/generate_random_password.html

  13. dr strangelove Says:

    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!

  14. Paul McKillop Says:

    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.

  15. mahinda Says:

    It’s a very usefull thing to me..

  16. Jamal Says:

    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_

  17. Nas Says:

    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.

  18. Arnold Daniels Says:

    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

  19. Arnold Daniels Says:

    I didn’t think

    tags would stick. I’ll retry to post the form using html entities. Lets see what happens ;)



    Username:
    Password:



  20. Arnold Daniels Says:

    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

  21. Arnold Daniels Says:


    $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);

  22. Dennis Pallett Says:

    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.

  23. Arnold Daniels Says:

    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

  24. nesianstyles Says:

    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.

  25. Nilesh Says:

    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 :)

  26. Errol Says:

    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).

  27. Vong Says:

    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…

  28. Joe Says:

    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

Leave a Reply

About the author
Dennis Pallett is the main contributor to PHPit. He owns several websites, including ASPit and Chill2Music. He is currently still studying.
Article Index
  1. Storing Passwords
  2. Handling Passwords
Bookmark Article
Download Article
PDF
Download this article as a PDF file