Storing Passwords Securely in Database and Cookies

Rkeast 0 Tallied Votes 502 Views Share

I am posting this code snippet as I continue to see login and user registration scripts that store passwords in databases as unsalted md5 hashes, and passwords in cookies as raw text.

This snippet includes an example user registration function, as well as an example user login, login check, and logout function.

XOR base64 functions are also included, as they are required for the login and login check. I did not write these functions and found them some time ago online. I can't remember where.

/*
The userRegister function creates inserts the user entry to the database
The user data is passed in as an array $userData.
For simplicity and example, our $userData will only contain the username
and the password. And we will assume that a database connection already exists.
*/
function userRegister($userData)
{
    //Sanitize data and strip array to variables
    foreach($userData as $k=>$v)
        $$k = mysql_real_escape_string($v);

    //Generate salt
    $creation_salt = sha1($username.':'.time());
    $password = sha1($password.':'.$creation_salt);

    //Insert user
    $sql = <<<EOT
INSERT INTO `users`
(
`username`,
`password`,
`creation_salt`,
)
VALUES
(
'$username',
'$password',
'$creation_salt',
)
EOT;

    mysql_query($sql);

    return mysql_insert_id();
}

/*
The userLogin function checks login data and creates cookies
The login data is passed in as an array $loginData.
For simplicity and example, our $loginData will only contain the username
and the password. And we will assume that a database connection already exists.
*/
function userLogin($loginData)
{
    //Sanitize data and strip array to variables
    foreach($userData as $k=>$v)
        $$k = mysql_real_escape_string($v);

    //Query DB for user entry
    $sql = "SELECT * FROM `users` WHERE `username` = '{$username}'";
    $result = mysql_query($sql);
    if(mysql_num_rows($result) > 0)
    {
        $row = mysql_fetch_array($result);

        $passwordHash = sha1($password.':'.$row['creation_salt']);
        if($passwordHash === $row['password'])
        {
            $time = time() + 3600;

            set_cookie('username', $username, $time);
            set_cookie('password', XOREncrypt($password, $row['creation_salt']), $time);
        }
    }
}

/*
The loggedIn function checks cookies to see if user is logged in
For simplicity we will assume that a database connection already exists.
*/
function loggedIn()
{
    if(isset($_COOKIE['username']))
    {
        $sql = "SELECT * FROM `users` WHERE `username` = '{$_COOKIE['username']}'";
        $result = mysql_query($sql);
        if(mysql_num_rows($result) > 0)
        {
            $row = mysql_fetch_array($result);

            $password = XORDecrypt($_COOKIE['password'], $row['creation_salt']);
            $passwordHash = sha1($password.":".$row['creation_salt']);
            return ($row['password'] === $passwordHash);
        }
    }
    return false;
}

/*
The logout function clears the cookies
*/
function logout()
{
    $time = time() - 3600;
    setcookie('username', '', $time);
    setcookie('password', '', $time);
}

//Below are the XOR base64 encryption and decryption functions
//I did not write these functions but found them online a long time ago
//I can't remember who the original author is, but they are credited for this code

/**
 * XOR encrypts a given string with a given key phrase.
 *
 * @param     string    $InputString    Input string
 * @param     string    $KeyPhrase      Key phrase
 * @return    string    Encrypted string    
 */    
function XOREncryption($InputString, $KeyPhrase){
 
    $KeyPhraseLength = strlen($KeyPhrase);
 
    // Loop trough input string
    for ($i = 0; $i < strlen($InputString); $i++){
 
        // Get key phrase character position
        $rPos = $i % $KeyPhraseLength;
 
        // Magic happens here:
        $r = ord($InputString[$i]) ^ ord($KeyPhrase[$rPos]);
 
        // Replace characters
        $InputString[$i] = chr($r);
    }
 
    return $InputString;
}
 
// Helper functions, using base64 to
// create readable encrypted texts:
 
function XOREncrypt($InputString, $KeyPhrase){
    $InputString = XOREncryption($InputString, $KeyPhrase);
    $InputString = base64_encode($InputString);
    return $InputString;
}
 
function XORDecrypt($InputString, $KeyPhrase){
    $InputString = base64_decode($InputString);
    $InputString = XOREncryption($InputString, $KeyPhrase);
    return $InputString;
}
Member Avatar for girishpadia
girishpadia

It is better to use session variables istead of Cookies.

digital-ether 399 Nearly a Posting Virtuoso Team Colleague
$creation_salt = sha1($username.':'.time());

This salt is way to easy to guess. Considering time() is a number of seconds, there are only 60*60 in an hour, and 60*60*24 in an hour: 86400 possible salts in one your.

Member Avatar for Rkeast
Rkeast

time() is the PHP function that returns the UNIX timestamp. I believe you are confusing this with another function that gets the time of the day in seconds.

digital-ether 399 Nearly a Posting Virtuoso Team Colleague

Since time() is a timestamp (number of seconds since Epoch), there are only 60*60 different values of time in one hour.

Here is an example of generating a more random value:

/**
	 * Generate a random hex based token
	 * @return String
	 * @param $length Int[optional]
	 */
	public static function generateToken($length = 40)
	{
		$token = array();
		for( $i = 0; $i < $length; ++$i )
		{
			$token[] =	dechex( mt_rand(0, 15) );
		}
		return implode('', $token);
	}

That generates a hex of 40 digits. See: http://www.bucabay.com/web-development/secure-password-hashing-storage-ph/ for the full code.

Rkeast commented: Sorry I didn't understand fully what you meant before. +1
Member Avatar for Rkeast
Rkeast

I see, sorry I didn't fully understand what you were saying before. I thought you were insinuating that time() was generating the time of day in seconds as a number between 1 and 86400

MrYrm 0 Light Poster

as commented above you really shouldn't save it to a cookie if your going for safety use sessions!

digital-ether 399 Nearly a Posting Virtuoso Team Colleague

Rkeast, no problem.

If you read the post on the page I linked, you'll see that using key based encryption such as:

XORDecrypt($_COOKIE['password'], $row['creation_salt']);

is also not advisable.

XORDecrypt also has a few problems in implementation. The main being the key is repeated over the length of the data being encrypted.

$rPos = $i % $KeyPhraseLength;

The modulo is being taken, which means passwords twice as long as the key can be deduced directly by comparing bits flipped in each corresponding byte of modulo $KeyPhraseLength.

It is possible to make a simplistic encryption as such suitable, but it would require generating a one time pad, which is a random key that is as long as the password. I believe one time pads are proven mathematically to be unbreakable with cryptanalysis.

Saving the salted MD5 in the cookie would be a lot better then using XORDecrypt.

However, it is best to use sessions. That way only the session ID is saved in a cookie. The session ID is generated randomly, when the user logs in, and is used to identify the user, for the rest of the session. That way you never need to save a password, even in encrypted form, on the client.

metalix 0 Posting Whiz in Training

Just as a note, Cookies are hackable. and not secure.
if you need security combine sessions a mysql database and an ip-address.
Combined with the correct hack-prevention you can be much more secure

Be a part of the DaniWeb community

We're a friendly, industry-focused community of developers, IT pros, digital marketers, and technology enthusiasts meeting, networking, learning, and sharing knowledge.