Activation token question for php : security

Hi,

I have been reading up a few web tutorials and stackexchange exchanges in regards to creating activation tokens, all of which gives various recommendations. I was hoping someone can clarify for me :

1. What is the underlying security concern? Are we afraid that a hacker could potentially generate an activation string and generate a new password for themselves?
2. If i use a token with expiration time,  wouldn't that fix that concern? Even if someone happens to guess an activation token, if the token is expired or is not in the database, the hacker would simply get an error message.
3. There are some concerns in making the token unique enough, which i am guessing is to avoid collision. Again, in an expiring token, wouldn't the chance of a collision be minimized?
4. In addition to the token, some have proposed hashing the token, and then combining the token with either the user id, the actual login, or the user email. What is the danger of any of the three? At this moment, i feel that including the user id with the token has the least possible chance of damage, as it is not used anywhere else for authentication. It is unique, but so what if a malicious attacker knows that there is user id 1 in a system?
5. Some have recommended an openssl implementation, which looks like the following :
<?php
function generateToken($length = 24) {
        if(function_exists('openssl_random_pseudo_bytes')) {
            $token = base64_encode(openssl_random_pseudo_bytes($length, $strong));
            if($strong == TRUE)
                return strtr(substr($token, 0, $length), '+/=', '-_,'); //base64 is about 33% longer, so we need to truncate the result
        }

        //fallback to mt_rand if php < 5.3 or no openssl available
        $characters = '0123456789';
        $characters .= 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz/+'; 
        $charactersLength = strlen($characters)-1;
        $token = '';

        //select some random characters
        for ($i = 0; $i < $length; $i++) {
            $token .= $characters[mt_rand(0, $charactersLength)];
        }        

        return $token;
}
?>

Open in new window

Can someone give me a pro and con to this solution? I am using a xampp windows server as a test server, so installation of modules always gets me jittery.
6. Recommendation of implementation tutorials that are actually secure / uptodate is also appreciated!

Thank you!
NeverEndingFlashStoriesAsked:
Who is Participating?
I wear a lot of hats...

"The solutions and answers provided on Experts Exchange have been extremely helpful to me over the last few years. I wear a lot of hats - Developer, Database Administrator, Help Desk, etc., so I know a lot of things but not a lot about one thing. Experts Exchange gives me answers from people who do know a lot about one thing, in a easy to use platform." -Todd S.

Ray PaseurCommented:
First off, I don't think there is any need to think about PHP < 5.3 any more.  PHP current levels are listed under Download on the home page.  PHP 5.3 has been obsolete for a long time.

I sometimes think of an activation token in the same way I think of an HTTP cookie.  You put a cookie on a browser so you can recognize the client during a return visit to your server.  If the cookie contains meaningful data, such as a key to an entry in a database, you have a risk of compromise.  What if an attacker generated a false cookie and presented it in the request?  You would want some way of knowing that it had been falsified so you could discard the request.  You might do that by creating a "check digit" solution.  Add a salt to the value in the cookie that provides some additional identifying information (maybe IP address of the client) and create the md5() string.  Store both the meaningful data and the md5() string in the cookie.  When the client presents the cookie, recreate the salt, regenerate the md5() string and compare it to the string that was presented.  If they match, you're probably OK.  Cookies have explicit expirations; your tokens would want that, too.  

You can guarantee UNIQUE tokens by marking a column in your database UNIQUE.  MySQL will throw error #1062 if you try to insert a duplicate into a UNIQUE column.  Expiring tokens would reduce the chances of a collision on a UNIQUE field, but only if you actually remove the row from the table.  You want to remove the row because the lookup time for UNIQUE verification increases as the table gets bigger.

Here is my simplified teaching example that illustrates parts of a tamper-resistant cookie design.
<?php // cookie_security.php

/**
 * Demonstrate how to encode information in a cookie
 * to reduce the risk of cookie tampering
 *
 * A salted message digest is included with the cookie
 *
 * If the message digest does not match the value of the cookie
 * we can assume that the cookie has been damaged and we can
 * discard it
 */
error_reporting(E_ALL);

// A DATA DELIMITER
$dlm = '|';

// YOUR OWN SECRET CODE - THE 'SALT' STRING
$secret_code = 'MY SECRET';

// A DATA STRING THAT WE WANT TO STORE (MIGHT BE A DATABASE KEY OR SIMILAR)
$cookie_value = 'MARY HAD A LITTLE LAMB';

// ENCODE THE DATA STRING TOGETHER WITH OUR SECRET
$cookie_code = md5($cookie_value . $secret_code);

// CONSTRUCT THE COOKIE STRING WITH THE CLEAR TEXT AND THE CODED STRING
$safe_cookie_value = $cookie_value . $dlm . $cookie_code;

// SET THE COOKIE LIKE "MARY HAD A LITTLE LAMB|cf783c37f18d007d23483b11759ec181"
setcookie('safe_cookie', $safe_cookie_value);



/**
 * WHEN STORED, THE COOKIE WILL BE URL-ENCODED SO IT WILL LOOK SOMETHING LIKE THIS ON THE BROWSER
 * MARY+HAD+A+LITTLE+LAMB%7Ccf783c37f18d007d23483b11759ec181
 * IT WILL BE URL-DECODED BEFORE IT IS PRESENTED TO PHP
 */

// HOW TO TEST THE COOKIE
if (isset($_COOKIE["safe_cookie"]))
{
    // BREAK THE COOKIE VALUE APART AT THE DELIMITER
    $array = explode($dlm, $_COOKIE["safe_cookie"]);

    // ENCODE THE DATA STRING TOGETHER WITH YOUR SECRET
    $cookie_test = md5($array[0] . $secret_code);

    // IF THE MD5 CODES DO NOT MATCH, THE COOKIE IS NO LONGER INTACT
    if ($cookie_test == $array[1])
    {
        echo "<br/>THE COOKIE {$_COOKIE["safe_cookie"]} IS INTACT";
    }
    else
    {
        // WHEN THE COOKIE HAS BEEN DAMAGED, DISCARD IT
        echo "<br/>THE COOKIE {$_COOKIE["safe_cookie"]} HAS BEEN CORRUPTED AND CANNOT BE USED";
        $_COOKIE['safe_cookie'] = NULL;
		setcookie('safe_cookie', NULL, time()-86400);
    }
}
else
{
    die('COOKIE IS SET - REFRESH THE BROWSER WINDOW NOW');
}




// MUNG THE COOKIE TO DEMONSTRATE WHAT HAPPENS WITH A CORRUPT COOKIE
$_COOKIE["safe_cookie"] = str_replace('MARY', 'FRED', $_COOKIE["safe_cookie"]);

// HOW TO TEST THE COOKIE
if (isset($_COOKIE["safe_cookie"]))
{
    // BREAK THE COOKIE VALUE APART AT THE DELIMITER
    $array = explode($dlm, $_COOKIE["safe_cookie"]);

    // ENCODE THE DATA STRING TOGETHER WITH OUT SECRET
    $cookie_test = md5($array[0] . $secret_code);

    // IF THE MD5 CODES DO NOT MATCH, THE COOKIE IS NO LONGER INTACT
    if ($cookie_test == $array[1])
    {
        echo "<br/>THE COOKIE {$_COOKIE["safe_cookie"]} IS INTACT";
    }
    else
    {
        echo "<br/>THE COOKIE {$_COOKIE["safe_cookie"]} HAS BEEN CORRUPTED AND CANNOT BE USED";
    }
}

Open in new window

Twitter and Facebook use OAuth access tokens.
https://en.wikipedia.org/wiki/OAuth
0
NeverEndingFlashStoriesAuthor Commented:
Hi Ray,

thanks for the response. I have read elsewhere when looking at hash algorithms for password encryption that md5 is considered insecure today, and I should be using hash 256 instead, with salt.

Are we more worried about uniqueness than encryption when it comes to generation of tokens?
0
Ray PaseurCommented:
Uniqueness is more important than encryption.  If you're generating duplicate keys, you've got a serious problem.  Encryption is like a fire safe.  Those things are rated against temperature and time.  If your tokens expire that takes care of the time element.  Brute-force computing may be able to crack the encryption, but it's not really that important.  It's just there to help you authenticate the validity of the token.  Consider an attacker who has to come up with the data for these rules, and then guess whether they would rather go elsewhere for low-hanging fruit.

1. The token must contain a randomly generated 9-character key that matches a key in your database.
2. The returned token must originate from the same IP address that requested the token.
3. The token md5() string must have been generated from a salt, the token, the IP address.
4. The token must not have expired.

And while I am sure that there are lots of people who will say that md5() is insecure, I disagree and offer this challenge.  I will buy anyone a beer who can tell me what salt and data strings were encoded into this digest:

e0f1299ed629d3c8826e2dd2be4780cf

To facilitate your search for the original input string, here is a link to the explanation of the md5() algorithm.
http://www.faqs.org/rfcs/rfc1321.html

And I have installed this easy-to-use script on my server.  Experiment here:
http://www.iconoun.com/demo/md5.php

But with all of that said, why not just use OAuth tokens?
0

Experts Exchange Solution brought to you by

Your issues matter to us.

Facing a tech roadblock? Get the help and guidance you need from experienced professionals who care. Ask your question anytime, anywhere, with no hassle.

Start your 7-day free trial
NeverEndingFlashStoriesAuthor Commented:
Thanks for the resources Ray,

I will look into OAuth. I think i was stuck with the various conflicting solutions that already exists in the project I inherited, and the online forums, but yes, you are correct, i shouldn't reinvent the wheel.
0
It's more than this solution.Get answers and train to solve all your tech problems - anytime, anywhere.Try it for free Edge Out The Competitionfor your dream job with proven skills and certifications.Get started today Stand Outas the employee with proven skills.Start learning today for free Move Your Career Forwardwith certification training in the latest technologies.Start your trial today
PHP

From novice to tech pro — start learning today.

Question has a verified solution.

Are you are experiencing a similar issue? Get a personalized answer when you ask a related question.

Have a better answer? Share it in a comment.