Crypt function doesn't work when comparing string to hash - php

I'm using a pretty standard way of cookie login - I give the user two cookies, one with his username and the other with a randomly generated string plus a user-specific salt.
This is what happens at login:
$_SESSION['username']=$row[username];
$_SESSION['user_id']=$row['id'];
$loginhash=generateRandomBase64String()."_".$row['salt'];
$number_of_days = 14;
$date_of_expiry = time() + 60 * 60 * 24 * $number_of_days ;
setcookie( "userlogin", $row['username'], $date_of_expiry, "/" ) ;
setcookie( "loginhash", $loginhash, $date_of_expiry, "/" ) ;
$cryptedhash=crypt($loginhash);
$today=date("Y-m-d");
mysql_query("update members set last_login='$today',loginhash='$cryptedhash' where id='$row[id]' ") or die(mysql_error());
So the $loginhash value is something like Pe0vFou8qe++CqhcJgFtRmoAldpuIs+d_g5oijF76 and the crypted version of that is stored in the database. The salt is already in the database, as it's generated for each user when they sign up.
I use session variables ($_SESSION[username]) to keep users logged in. Then, when a user visits the site, I check for two things: if $_SESSION[username] is not set but $_COOKIE[userlogin] is, I check if the hash is correct so I could log the user in. The problem is, the hash is never correct.
if($_COOKIE['userlogin'] && !isset($_SESSION[user_id])){
$username=mysql_real_escape_string($_COOKIE['userlogin']);
$loginhash=mysql_real_escape_string($_COOKIE['loginhash']);
$salt=substr($loginhash,-8);
$result=mysql_query("select * from members where (username='$username' || email='$username') && salt='$salt' limit 1 ") or die (mysql_error());
$row=mysql_fetch_assoc($result);
$cryptedhash=$row['loginhash'];
if (crypt($loginhash, $cryptedhash) == $cryptedhash){
$_SESSION['username']=$row[username];
$_SESSION['user_id']=$row['id'];
}
}
$_COOKIE[userlogin] is the correct value. When I check for the username/salt combination in the database, the correct result is found (echo $row[username] gives correct value). However, the if condition below that is never met. I would think there's something weird about my PHP configuration, but I use the same crypting mechanism to store passwords and there it works properly.
So can anyone see what's going wrong here?
PS I'm not looking to start a discussion about cookie safety or the variety of available hashing functions here.

Here's the problem:
In your first call to crypt(), you do not specify a salt.
In your second call to crypt(), you pass the $cryptedhash as the salt.
crypt() is documented to generate a random salt if you do not provide one, and then prepend that salt to the returned hash. That has the side effect that if you pass a returned salt+hash as the hash for a subsequent call, crypt() will still pull the correct salt out of it.
Unfortunately the algorithm used and the length/format of the salt+hash is based on a combination of your operating system, PHP version, and whether or not you specified the salt parameter. When you used your code previously, you had the happy accident that DES was chosen in both calls to crypt(). Now, your environment is using a different algorithm for the 2 calls to crypt() since you only supplied the hash in one of them.
The solution is to just pass a consistent salt to both calls to crypt(). You can stop appending the salt to the string you want to hash, and actually pass your user salt as the salt parameter and everything will be fine.

This line is wrong $loginhash=mysql_real_escape_string($_COOKIE['loginhash']); You don't need to escape it (you are not using it with the database), you must use it unaltered when pass it to crypt(), so the line can be writen as $loginhash=$_COOKIE['loginhash']; (at least for this part of code)

If you are using PHP Version 5.5.0 or higher you should take a look at PHPDoc - Password Hashing!
I know you said you don't want to discuss different hashing functions, but I thought this one makes it easyer for you since it should get rid of your problem and is (in my oppinion) way easier to use!
Here would be your code with the new functions: (not tested, if broke pleases comment)
$_SESSION['username']=$row[username];
$_SESSION['user_id']=$row['id'];
$loginhash=generateRandomBase64String()."_".$row['salt'];
$number_of_days = 14;
$date_of_expiry = time() + 60 * 60 * 24 * $number_of_days ;
setcookie( "userlogin", $row['username'], $date_of_expiry, "/" ) ;
setcookie( "loginhash", $loginhash, $date_of_expiry, "/" ) ;
$cryptedhash=password_hash($loginhash, PASSWORD_DEFAULT, array("cost" => 10));
// a cost of 10 is standard, you may want to adjust it according to your hardware (lower/higher cost means faster/slower)
$today=date("Y-m-d");
mysql_query("update members set last_login='$today',loginhash='$cryptedhash' where id='$row[id]' ") or die(mysql_error());
Using PASSWORD_DEFAULT there will make shure that even in future versions the strongest algorithm will always be used.
and
if($_COOKIE['userlogin'] && !isset($_SESSION[user_id])){
$username=mysql_real_escape_string($_COOKIE['userlogin']);
$loginhash=$_COOKIE['loginhash']; // i guess you should not use mysql_real_escape_string
$salt=mysql_real_escape_string(substr($loginhash,-8)); // here would be the place to use it
$result=mysql_query("select * from members where (username='$username' || email='$username') && salt='$salt' limit 1 ") or die (mysql_error());
$row=mysql_fetch_assoc($result);
$cryptedhash=$row['loginhash'];
if (password_verify($loginhash, $cryptedhash)){
$_SESSION['username']=$row[username];
$_SESSION['user_id']=$row['id'];
}
}

Related

Verification link with email and hashed user id with salt string. Is it secure?

what do you think about creating a verification link having a querystring made up with parameters as email and hash?
email is the html encoded plain string email of the registered user,
hash is the result value of the PHP function password_hash where the string for the first parameter is the unique id user number selected from the database with a fixed salt string.
I was thinking about also to toggle the salt, but one can guess the id user number for a given email, passing it to password_hash and obtaining a valid hash for the querystring (even if password_hash adds automatically a salt on his own.), so I think it isn't secure.
I thought something like this:
// the result of SELECT from the database (I use Mysql)
$id = '5';
// fixed salt string
$salt = 'xbrENiBq6kb87WrZYhxS';
$email = urlencode($email);
$key = $id . $salt;
$hash = urlencode(password_hash($key, PASSWORD_DEFAULT));
// the resulted querystring:
$url = "somesite.com/index.php?email=$email&hash=$hash";
then I will send that to the user email.
Please, can you tell me, what do you think about? Many Thanks!
Depends on your encoding, you can try with mixing up various types of encode to reach to the real value. Your final result to compare with the database must be a md5 string, this way you don't lose performance as well.
SELECT md5(your_field) FROM table
Sorry for the bad English.

While registering Get registration datetime and hash it

I just want to hash my user password by taking the registration date_time as a salt. I am going to use this function:
function create_hash($pass, $created_date, $hash_method = 'md5') {
// the salt will be the reverse of the user's created date
// in seconds since the epoch
$salt = strrev(date('U', strtotime($created_date));
if (function_exists('hash') && in_array($hash_method, hash_algos()) {
return hash($hash_method, $salt.$pass);
}
return md5($salt.$pass);
}
And also similar method for password verification. But I am thinking about registration process. In my registration form I have only field of username and password and in my mysql database the creation date will be added automatically.
But when first time registration I also need the creation_date for hashing password? How to overcome this? So basically I am imagining I have to get the creation_date before inserting data in the database for salting. How to do this?
One approach I am thinking, in my registration form before calling this function I will do this:
$created_date = date("Y-m-d H:i:s");
$salt = strrev(date('U', strtotime($created_date));
But I am afraid is there going to be a fraction time difference between this salt time and creation_date time of database. If so then my password_validation will not work..
*** BY the way I know md5() is not secured one. So I dont need suggestion about md5 or other hashing algorithm. I am just interested about salting
Maeby is better idea to write a function that creates a salt
function salt($n=3)
{
$key = '';
$pattern = '1234567890abcdefghijklmnopqrstuvwxyz.,*_-=+##!&^%';
$counter = strlen($pattern)-1;
for($i=0; $i<$n; $i++)
{
$key .= $pattern{rand(0,$counter)};
}
return $key;
}
and when new user are registered to call this funtion and do something like this:
$salt=salt();
$hash_pass=md5(md5($_POST['password']).$salt);
mysql_query("INSERT INTO `my_table` SET `name`='".$_POST["name"]."', `pass`='".$hash_pass."' `regDate`=NOW() ");
When the user is loged in, you select the user data from table as name and compare his hashed password with posted password with his salt
The best salt you can have, is not derrived from any other parameters, and is not predictable. On a deterministic computer, the best you can do, is getting the salt from the random source of the operating system. This random source will collect real chance from events like user actions, boot time, and much more...
PHP already has a dedicated function password_hash() to generate a safe BCrypt hash with such a random salt. It includes the salt into the resulting hash-value, so you can store it into a single field in the database.
// Hash a new password for storing in the database.
// The function automatically generates a cryptographically safe salt.
$hashToStoreInDb = password_hash($password, PASSWORD_BCRYPT);
// Check if the hash of the entered login password, matches the stored hash.
// The salt and the cost factor will be extracted from $existingHashFromDb.
$isPasswordCorrect = password_verify($password, $existingHashFromDb);
Using a pepper can be a good idea as well, but there are better ways to add such a server side secret. Whether your pepper contains special characters or is just longer doesn't matter, the entropy is important. If you are interested you may have a look at my tutorial about secure password storing, on the last pages i tried to explain the pros and cons of a pepper.

best practice to generate random token for forgot password

I want to generate identifier for forgot password . I read i can do it by using timestamp with mt_rand(), but some people are saying that time stamp might not be unique every time. So i am bit of confused here. Can i do it with using time stamp with this ?
Question
What's best practice to generate random/unique tokens of custom length?
I know there are lot of questions asked around here but i am getting more confused after reading different opinion from the different people.
In PHP, use random_bytes(). Reason: your are seeking the way to get a password reminder token, and, if it is a one-time login credentials, then you actually have a data to protect (which is - whole user account)
So, the code will be as follows:
//$length = 78 etc
$token = bin2hex(random_bytes($length));
Update: previous versions of this answer was referring to uniqid() and that is incorrect if there is a matter of security and not only uniqueness. uniqid() is essentially just microtime() with some encoding. There are simple ways to get accurate predictions of the microtime() on your server. An attacker can issue a password reset request and then try through a couple of likely tokens. This is also possible if more_entropy is used, as the additional entropy is similarly weak. Thanks to #NikiC and #ScottArciszewski for pointing this out.
For more details see
http://phpsecurity.readthedocs.org/en/latest/Insufficient-Entropy-For-Random-Values.html
This answers the 'best random' request:
Adi's answer1 from Security.StackExchange has a solution for this:
Make sure you have OpenSSL support, and you'll never go wrong with this one-liner
$token = bin2hex(openssl_random_pseudo_bytes(16));
1. Adi, Mon Nov 12 2018, Celeritas, "Generating an unguessable token for confirmation e-mails", Sep 20 '13 at 7:06, https://security.stackexchange.com/a/40314/
The earlier version of the accepted answer (md5(uniqid(mt_rand(), true))) is insecure and only offers about 2^60 possible outputs -- well within the range of a brute force search in about a week's time for a low-budget attacker:
mt_rand() is predictable (and only adds up to 31 bits of entropy)
uniqid() only adds up to 29 bits of entropy
md5() doesn't add entropy, it just mixes it deterministically
Since a 56-bit DES key can be brute-forced in about 24 hours, and an average case would have about 59 bits of entropy, we can calculate 2^59 / 2^56 = about 8 days. Depending on how this token verification is implemented, it might be possible to practically leak timing information and infer the first N bytes of a valid reset token.
Since the question is about "best practices" and opens with...
I want to generate identifier for forgot password
...we can infer that this token has implicit security requirements. And when you add security requirements to a random number generator, the best practice is to always use a cryptographically secure pseudorandom number generator (abbreviated CSPRNG).
Using a CSPRNG
In PHP 7, you can use bin2hex(random_bytes($n)) (where $n is an integer larger than 15).
In PHP 5, you can use random_compat to expose the same API.
Alternatively, bin2hex(mcrypt_create_iv($n, MCRYPT_DEV_URANDOM)) if you have ext/mcrypt installed. Another good one-liner is bin2hex(openssl_random_pseudo_bytes($n)).
Separating the Lookup from the Validator
Pulling from my previous work on secure "remember me" cookies in PHP, the only effective way to mitigate the aforementioned timing leak (typically introduced by the database query) is to separate the lookup from the validation.
If your table looks like this (MySQL)...
CREATE TABLE account_recovery (
id INTEGER(11) UNSIGNED NOT NULL AUTO_INCREMENT
userid INTEGER(11) UNSIGNED NOT NULL,
token CHAR(64),
expires DATETIME,
PRIMARY KEY(id)
);
... you need to add one more column, selector, like so:
CREATE TABLE account_recovery (
id INTEGER(11) UNSIGNED NOT NULL AUTO_INCREMENT
userid INTEGER(11) UNSIGNED NOT NULL,
selector CHAR(16),
token CHAR(64),
expires DATETIME,
PRIMARY KEY(id),
KEY(selector)
);
Use a CSPRNG When a password reset token is issued, send both values to the user, store the selector and a SHA-256 hash of the random token in the database. Use the selector to grab the hash and User ID, calculate the SHA-256 hash of the token the user provides with the one stored in the database using hash_equals().
Example Code
Generating a reset token in PHP 7 (or 5.6 with random_compat) with PDO:
$selector = bin2hex(random_bytes(8));
$token = random_bytes(32);
$urlToEmail = 'http://example.com/reset.php?'.http_build_query([
'selector' => $selector,
'validator' => bin2hex($token)
]);
$expires = new DateTime('NOW');
$expires->add(new DateInterval('PT01H')); // 1 hour
$stmt = $pdo->prepare("INSERT INTO account_recovery (userid, selector, token, expires) VALUES (:userid, :selector, :token, :expires);");
$stmt->execute([
'userid' => $userId, // define this elsewhere!
'selector' => $selector,
'token' => hash('sha256', $token),
'expires' => $expires->format('Y-m-d\TH:i:s')
]);
Verifying the user-provided reset token:
$stmt = $pdo->prepare("SELECT * FROM account_recovery WHERE selector = ? AND expires >= NOW()");
$stmt->execute([$selector]);
$results = $stmt->fetchAll(PDO::FETCH_ASSOC);
if (!empty($results)) {
$calc = hash('sha256', hex2bin($validator));
if (hash_equals($calc, $results[0]['token'])) {
// The reset token is valid. Authenticate the user.
}
// Remove the token from the DB regardless of success or failure.
}
These code snippets are not complete solutions (I eschewed the input validation and framework integrations), but they should serve as an example of what to do.
You can also use DEV_RANDOM, where 128 = 1/2 the generated token length. Code below generates 256 token.
$token = bin2hex(mcrypt_create_iv(128, MCRYPT_DEV_RANDOM));
This may be helpful whenever you need a very very random token
<?php
echo mb_strtoupper(strval(bin2hex(openssl_random_pseudo_bytes(16))));
?>

Error after INSERT INTO

I've the following code:
$salt=uniqid(mt_rand(), false);
#Add data to tables
mysql_query("INSERT INTO accounts VALUES('$user', '".hash('sha512',$pass+$salt)."', '$salt', '$cookie_value')");
mysql_query("INSERT INTO passwordreset VALUES('$user', NULL, NULL)");
#cookie creation
#.....
#cookie update
mysql_query("UPDATE accounts SET cookie='$cookie_value' WHERE user='$user'");
I sanitize data from form using these functions:
$var = htmlentities($var, ENT_QUOTES, "UTF-8");
return mysql_real_escape_string($var);
Today I logged into phpMyAdmin and I saw that passwords and salts for all users are the same.
Don't remind me about deprecated mysql_* I know it, that's just quick draft.
String concatenation in PHP uses . not +. Thus:
hash('sha512',$pass+$salt)
Should be
hash('sha512',$pass.$salt) // or
hash('sha512',"${pass}${salt}")
This is PHP, $pass+$salt should be $pass . $salt
There's a few things I would comment on your current code:
Using the + operator (as opposed to .) on two strings results in the sum of both values cast to integer (if a string is not numeric it's cast to int(0)); when it's passed to hash() it gets cast to a string again, so your passwords will typically all be sha512("0"). I'm not sure why your salts all have the same value though, unless the column data type is INT in your database.
You can use uniqid(mt_rand(), true) to collect more entropy, resulting in a better salt.
You should hash passwords with a dedicated password hash, such as the BlowFish option in crypt() (make sure your column width is big enough); this way you can get rid of the salt column and you can choose how much work is required to verify the hash in a backward compatible manner.
The cookie column is for an auto-login feature I assume? It's better to create a separate table for this containing a randomized string as the primary key and a foreign key to your users table. This way you can support auto-login from multiple browsers.
error here
$pass+$salt
should be
$pass.$salt
. is used for string catenation in php

How to check hash collision

I created a function in php that generates a hash from a number (id), and I need to check that there will be no collision (two or more ids have the same hash).
Which function I can use to verify that there will be no collision in the nexts 99999999 ids?
Thanks!
If your hash function works as supposed, and always generates the same output for the same input. And your inputs are restricted to 99999999 numbers, you could simply generate the hashes for those numbers and verify that there are no duplicates.
Although the nice solution would be to demonstrate mathematically that your hash function will produce unique results for those numbers.
If the hash can be totally random, try using the current timestamp in it as additional randomizer. For example:
$hash = sha1(microtime() * rand(1, 9999));
The chances of a duplicate coming out there is rather slim. Additionally, try setting the database field to be a UNIQUE field, ensuring a duplicate INSERT is impossibe. Then, to make things complete, you can create a loop that tries until it succeeds, like so:
// SHA1 values shouldn't need escaping, but it doesn't really hurt to be extra sure :)
$query = "INSERT INTO `table` (`hash`) VALUES('" . mysql_real_escape_string($hash) . "')";
// Let's try the insert with a max of 10 random hashes
$tries = 10;
while(mysql_query($query) !== true) {
if($tries <= 0) {
break; // Something is really failing, stop trying!
}
// If this point is reached, apparantly a duplicate was created. Try again.
$hash = sha1(microtime() * rand(1, 9999));
// Decrement the tries counter.
$tries--;
}

Categories