best practice to generate random token for forgot password - php

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

Related

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.

Crypt function doesn't work when comparing string to hash

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'];
}
}

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

Unique, unpredictable, 12 digit, integer id

How would I go about generating this... I want to keep my primary key sequential and have a 12 digit unique pin generated for each new object added to the database.
The reason it cant just be autoincrement is i don't want the sequential numbers to be easily guessable.
It needs to be integer numbers, because I'm going to have verification codes that need to be dialed on a phone pad.
Use a concatenation of a unique incremented number and a randomly generated number.
The unique incremented number ensures that the result is unique, and the randomly generated number makes it hardly guessable.
This is simple and guaranteed to have no collision (1). The result is incremental, partly random, and non-predictable (provided that the random number part is generated with a good PRNG).
(1): You have to either pad id and random with zeros, or to separate them with some non-digit character.
With a MySQL db, this translates to:
CREATE TABLE foo (
id int not null auto_increment,
random int not null,
...
primary key (id)
);
Maybe you can use UUID_SHORT(). Not 12 digits long, but still could be a viable option:
mysql> select uuid_short();
+-------------------+
| uuid_short() |
+-------------------+
| 22048742962102272 |
+-------------------+
So:
INSERT INTO `table` (`id`, `text`) VALUES (UUID_SHORT(), 'hello world!');
Note: If you really want to have exactly 12 digits, then don't even try to substring the result, if would not ensure the uniqueness of the identifier and may cause collisions.
<?php
$allowed_characters = array(1,2,3,4,5,6,7,8,9,0);
for($i = 1;$i <= 12; $i++){
$pass .= $allowed_characters[rand(0, count($allowed_characters) - 1)];
}
echo $pass;
?>
demo: http://sandbox.phpcode.eu/g/c0190/4
Generally, I will prefer to do something a little bit more low tech. I obscure the values in PHP and leave them as auto-incrementing in JS.
$seeds = array( /*series 100 of very large >= 10-digit numbers*/ );
$seedID = rand( count( $seeds ) ); // randomly choose one of those.
// a string combination which represents the ID + some hash.
$id = bcadd( $seeds[ $seedID ], /* id retrieved from database */ );
// make sure we've not accidentally passed the 10^12 point
$id = bcmod( $id, 1000000000000 );
// make sure to pad
$id = str_pad('' . $id, 3, "0", STR_PAD_LEFT);
$outID = substr( $id, 0, 5 ) . $seedID . substr( $id, 6 );
Then, when receiving the ID from the user:
$seedID = substr( $outID, 6, 2 );
$tmpID = substr( $outID, 0, 5 ) . substr( $outID, 8 );
$id = bcsub( $tmpID, $seeds[ $seedID ] );
// we passed the modulus se we need to add this back in.
if( $id < 0 ) $id = bcmod( bcadd( $id, 1000000000000 ), 1000000000000 );
This will basically mean that you're simply obscuring whatever number you want -- you can use auto_increment with impunity!
One method would be to take your primary key value, salt it with a few other random-ish bits of data (username, current time, process ID, fixed string, etc...) and hash it with md5 or sha1. You then take the hash string and convert it into digits via basic string operations. That'll give you a relatively unique numeric code.
of course, with only 12 digits, you're far more likely to end up with a collision than by using the raw string hash - but since you're requiring this to be dialed on a keypad, it's an acceptable tradeoff.
If the pins are invalidated/deleted after usage, then the collision chances will be much reduced.
You want two things
Uniqueness
Incremental
If you want both the things from same sequence you will run out of luck (literally)
Uniqueness is guaranteed by having large sample space + random + check-unique. Which means, the actual number could be anywhere in between the sample space.
But if you want unique + incremental property, you are dividing sample space by 2. In 64 tries you would have reduced a 64 bit int sample space to 1 bit sample space.
Good luck !
All solutions so far lack one thing essential to your application: Security!
You said you will be using these numbers as a (product) verification code - so you really, really want this to be unpredictable, otherwise it will get exploited.
Neither MySQL's built-in RANDOM function nor any of the random functions PHP provides today are secure random functions. They behave pseudo-randomly, alright, but they all are predictable!
Your only chance is to whip up something of your own using /dev/urandom on a *nix machine or leveraging the Crypto API on Windows. OpenSSL does provide secure random numbers based on these mechanisms - you could reuse this either in a C extension for PHP or by reading the output from a command line script called from PHP. See also this answer.
About your requirement for the numbers to be sequential - is this really so important? It does complicate things enormously. Otherwise you would be good to go with a simple secure 6 byte random number encoded to a string using hex encoding (yielding a 12 character string). Although I would recommend making it 10 bytes and 20 characters to be safer.
But if you want to be sequential, which I interpret as monotonously increasing (because a simple +1 would be trivially predictable), this makes things just so much more complicated. And you don't gain anything from this complexity, the only thing that might happen is that you break the security by inventing some obscure scheme that is easily exploitable.
My suggestion: Add another column that acts as a plain old auto-incremented ID and add the code as a random number constructed as above as a separate column. As far as I see, there's no need to require the product activation code to be the ID at the same time.

Generating Strong Unique User ID's w/PHP & MySQL

Ahoy Stack Overflow! This be mai first post...
I'm attempting to identify users with a salted unique public key.
Algorithm - Should I use uniqid(), sha256, sha512, something else? All hashes will be salted. NIST recommended SHA256, but I prefer to hear what others might suggest.
Generation - Does hash(SALT + AUTO_INCREMENT_PK + CREATED_TIMESTAMP) suffice? More entropy?
I'd use email, as it is unique for each user, however the user can modify their email address. I was also considering storing signup_email so that hashes would not have to be re-calculated.
MySQL Storage - Currently, our ID's are INT(255) auto_increment primary key's. As stated earlier, potentially hundreds of millions of keys. Depending on the crypto algo, I should have a fixed-size ID. Can I keep INT(255) or should I use CHAR(n)?
---------------------- Thanks for reading :) -------------------------------
One thing: If you don't trust the users with their IDs, sending them over GET or POST will not work; those are all visible to motivated users.
I would use SHA256 using a salt.counter.time string, and use the output to generate GUIDs for the actual id. This would minimize the possibility for collisions.
You will have to use CHAR for MySQL to store GUIDs.
See the comments at http://us2.php.net/manual/en/function.uniqid.php for more in-depth info. AFAIK GUID is not part of the PHP core so you have to fake it a bit.
If you are using user id as the way to allow a user to do anything with your service, if one user "guesses" the user id of another one, he'll be able to do whatever he wants with that one's account ?
You do not have any kind of other password or anything to go along that ?
Well, in that case, you need something quite unique, don't you ;-)
(Hoping I understood the question well -- but that might not be the case -- sorry, if it isn't)
What do you think of using Globally Unique Identifier (like, for instance, 61350955-9755-4AF3-8C19-6DBC42CA69E2) for your users ?
For an example of how they look like, take a look at http://createguid.com/
As a sidenote, that GUID is quite long ; which means lots of bytes in your DB, if you have millions users... So, it probably shouldn't be used as any kind of primary/foreign key.
What about using the smallest possible integer (that fits the number of users you'll have) as primary/foreign key, as that one will be duplicated in many places of the application ; and only have the "long user id" stored only once, in your user table ?
I wrote this class that gives you an unique id of 24 chars, compatible with the id field of MongoDB (and using the same logic to construct it). Might be useful in the future.
<?php
/**
* Generator for Mongo-like ObjectIds in pure PHP
* Author: Mauricio Piacentini
*
* Inspired by https://github.com/justaprogrammer/ObjectId.js
*
*/
class ObjectIdFactory
{
private $_datetime = null;
private $_machine = null;
private $_pid = null;
private $_increment = null;
public function __construct()
{
$this->_machine = str_pad(dechex(rand(0, 16777215)), 6, "0", STR_PAD_LEFT);
$this->_pid = str_pad(dechex(rand(0, 32767)), 4, "0", STR_PAD_LEFT);
$this->_increment = rand(0, 16777215);
//We need a DateTime object to get timestamps, cache it
$this->_datetime = new DateTime();
}
public function getNewId($forcedincrement = null)
{
if (is_null($forcedincrement)) {
$this->_increment++;
if ($this->_increment > 0xffffff) {
$this->_increment = 0;
}
} else {
$this->_increment = $forcedincrement;
}
$timestamp = $this->_datetime->getTimestamp();
$timestamp_final = str_pad(dechex($timestamp), 8, "0", STR_PAD_LEFT);
$increment_final = str_pad(dechex($this->_increment), 6, "0", STR_PAD_LEFT);
return $timestamp_final . $this->_machine . $this->_pid . $increment_final;
}
}
https://github.com/piacentini/ObjectId.php
Have you looked into using a UUID?
A quick google search yields some good resources/links.
Personally I use md5(uniqid(mt_rand(), true)) which will create 32 character identifier (a 128 bit hex number) that is extremely difficult to predict.

Categories