15

I would like to create a site-wide hash to be used as salt in creating password retrieval tokens. I have been bouncing around stackoverflow trying to get a sense of the best way to do this.

Here's the reset process:

When a user requests a password reset email the code generates a retrieval token:

$token = hash_hmac('sha256', $reset_hash* , $site_hash)

*$reset_hash is a hash created using phpass HashPassword() function, saved in the user table.

I then send the token in a URL to the users email address. They click before the token times out in an hour. I match their submission with the a challenge token generated server-side. If it matches, then they are forced to choose a new password, and then login.

I would like to know the best way to generate the $site_key. I am thinking of using another HMAC hash that is seeded by random numbers:

$site_key = hash_hmac('sha256', MCRYPT_DEV_RANDOM, MCRYPT_DEV_RANDOM);

This produces something like this:

98bb403abbe62f5552f03494126a732c3be69b41401673b08cbfefa46d9e8999

Will this be a suitably random to be used for this purpose? Am I overcomplicating this, or approaching it the wrong way?

I was inspired to use HMAC by this answer

EDIT: I am trying to avoid a 'secret question' step urged by some of my coworkers, so I would like the reset link to provide a single step to resetting the password. Therefore, my concern is that this process be secure enough to safeguard a system containing sensitive information.

RESOLVED, for now: I am going to go with a nonce as described by The Rook as the reset token. Thanks everyone for the comments and feedback.

Community
  • 1
  • 1
Todd Holmberg
  • 450
  • 1
  • 4
  • 13
  • I'd have to say this is way overcomplicated. I use a simple 6 character random alphanumeric token, with a timeout like you said, and basic brute-force detection. – Fosco Jul 20 '10 at 13:22
  • http://stackoverflow.com/questions/1416060/if-i-make-the-salt-random-for-each-user-how-do-i-authenticate-them # read this, it should cover most the concepts you need to understand. – Kent Fredric Jul 20 '10 at 13:23
  • @Fosco: by basic-brute-force detection do you mean limiting the number of times the token can be authenticated within the allowed reset time? – Todd Holmberg Jul 20 '10 at 14:14
  • @Kent: So you think a site-wide salt is a bad idea vs. saving a temporary salt for the purposes of password resetting? I already protect the passwords with a vetted hashing algorithm (phpass), that incorporates a random salt per user. I think this adequately protects the passwords. I am less sure about my password reset procedures. – Todd Holmberg Jul 20 '10 at 14:19
  • 1
    @Todd: I log the number of authentication attempts from an IP address, and begin discarding them after a set limit. The idea is that in brute force, they wouldn't have the token and would be trying to get it. Unlikely, but sometimes you have to try. Another possibility here is to just use a GUID/UUID and forget everything else. – Fosco Jul 20 '10 at 14:21
  • @Fosco: Do you mean that you would use UUIDs as user salts? Or can they be used in some way to negate the need for brute-force detection and hash matching? – Todd Holmberg Jul 20 '10 at 15:41
  • @Todd: I meant instead of a hash/salt. You can make an entry in a Reset table for that User ID with a new UUID, Email them the link that has the UUID, and when they click it give them the page to enter a new password. – Fosco Jul 20 '10 at 16:47

1 Answers1

24

To start with, your not talking about a salt. You're talking about a Cryptographic Nonce, and when you salt a password you should use a Cryptographic Nonce. In the case of resetting passwords, it should be a random number that is stored in the database. It is not advantageous to have have a "site salt".

First and foremost I don't like uniqid() because it's a time heavy calculation and time is a very weak seed. rand() vs mt_rand(), spoiler: rand() is total crap.

In a web application a good source for secure secrets is non-blocking access to an entropy pool such as /dev/urandom. As of PHP 5.3, PHP applications can use openssl_random_pseudo_bytes(), and the Openssl library will choose the best entropy source based on your operating system, under Linux this means the application will use /dev/urandom. This code snip from Scott is pretty good:

function crypto_rand_secure($min, $max) {
        $range = $max - $min;
        if ($range < 0) return $min; // not so random...
        $log = log($range, 2);
        $bytes = (int) ($log / 8) + 1; // length in bytes
        $bits = (int) $log + 1; // length in bits
        $filter = (int) (1 << $bits) - 1; // set all lower bits to 1
        do {
            $rnd = hexdec(bin2hex(openssl_random_pseudo_bytes($bytes)));
            $rnd = $rnd & $filter; // discard irrelevant bits
        } while ($rnd >= $range);
        return $min + $rnd;
}

function getToken($length=32){
    $token = "";
    $codeAlphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
    $codeAlphabet.= "abcdefghijklmnopqrstuvwxyz";
    $codeAlphabet.= "0123456789";
    for($i=0;$i<$length;$i++){
        $token .= $codeAlphabet[crypto_rand_secure(0,strlen($codeAlphabet))];
    }
    return $token;
}
rook
  • 66,304
  • 38
  • 162
  • 239
  • @TheRook: Thanks for this. Would you advise using $password_token as the full token. Would it be better to use the nonce to salt $reset_hash to create a token that is not stored in the database, but verifiable when the user clicks the reset URL in the email? Would this, again, be overdoing it? I am trying to avoid a 'secret question' step urged by some of my coworkers, so I would like the reset link to provide a single step to resetting the password. – Todd Holmberg Jul 20 '10 at 16:45
  • I hate the "Secret question" stuff, generally its one of those things that makes me go "no longer interested" in your site. But I'm always paranoid somebody will hijack my email account and then reset all my passwords .... you can't win. Also, tried supporting OpenID? Its Awesome ! – Kent Fredric Jul 20 '10 at 16:48
  • Better use the modified Base-64 character set for URLs (see http://tools.ietf.org/html/rfc4648#section-5). – Gumbo Jul 20 '10 at 16:52
  • Would a nonce generated using this method be better to use than a UUID? – Todd Holmberg Jul 20 '10 at 17:50
  • 2
    @Todd Holmberg taking the hash value of a nonce doesn't make it more random, just use this value. – rook Jul 20 '10 at 19:24
  • @Todd Holmberg UUID's are massive, i am not exalt sure how they are generated so i cannot say if its more secure. I know it contains a microtime, such that you never have to worry about the same value being generated twice, where as this value is more of a "pure" random number and the same value could be generated multiple times. `uniqid()` like a UUID will always produce a unique value. So it depends on your needs. – rook Jul 20 '10 at 19:32
  • 2
    @Gumbo,@TheRook Found this in comments of the [base64_encode()](http://us2.php.net/manual/en/function.base64-encode.php) page: `$output = strtr(base64_encode($input), '+/=', '-_,')` – Todd Holmberg Jul 20 '10 at 19:43
  • I've implemented this solution, but testing it I see very similar strings generated after applying base64_encode function (examples: MTI5MTMxODYwNA,, MTI5MTMxODYzNQ,, MTI5MTMxODY3MQ,,), nonces() are totally diferent though. Is this normal? – Pherrymason Dec 02 '10 at 19:54
  • @clinisbut the vales should be very different, the ones you posted look a lot alike, which doesn't look right. – rook Dec 02 '10 at 19:56
  • Ok, explaining the problem always helps to find the solution, it happened I was base64_encoding() a wrong value (a timestamp in fact, that explains the similarity) :S my fault. – Pherrymason Dec 02 '10 at 20:06
  • well. i like this so much. but can anyone update this? since this is 2 years old. – Loonb Sep 05 '12 at 23:44
  • Since this answer was posted, php has added [random_bytes](https://www.php.net/manual/en/function.random-bytes.php) which may be a better option than openssl_random_pseudo_bytes. In researching, I'm seeing [others suggest](https://stackoverflow.com/a/18890309/3658757) code like `$token = bin2hex(random_bytes(16));`. Is there a reason why one shouldn't generate a token more directly like in this example? – Obscerno Oct 18 '21 at 19:54