best practice to generate random token for forgot password












83















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.










share|improve this question

























  • @AlmaDoMundo: A computer can't divide time unlimited.

    – juergen d
    Sep 20 '13 at 7:15











  • @juergend - sorry, do not get that.

    – Alma Do
    Sep 20 '13 at 7:15











  • You will get the same timestamp if you call it for instance a nano second apart. Some time functions for instance can only return time in 100ns steps, some only in seconds step.

    – juergen d
    Sep 20 '13 at 7:17













  • @juergend ah, that. Yes. I mentioned 'classic' timestamp with seconds only. But if act like you've said - yes (that only leaves us an option with time machine to get non-unique timestamp)

    – Alma Do
    Sep 20 '13 at 7:18






  • 1





    Head's up, the accepted answer does not leverage a CSPRNG.

    – Scott Arciszewski
    Jul 14 '15 at 22:47
















83















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.










share|improve this question

























  • @AlmaDoMundo: A computer can't divide time unlimited.

    – juergen d
    Sep 20 '13 at 7:15











  • @juergend - sorry, do not get that.

    – Alma Do
    Sep 20 '13 at 7:15











  • You will get the same timestamp if you call it for instance a nano second apart. Some time functions for instance can only return time in 100ns steps, some only in seconds step.

    – juergen d
    Sep 20 '13 at 7:17













  • @juergend ah, that. Yes. I mentioned 'classic' timestamp with seconds only. But if act like you've said - yes (that only leaves us an option with time machine to get non-unique timestamp)

    – Alma Do
    Sep 20 '13 at 7:18






  • 1





    Head's up, the accepted answer does not leverage a CSPRNG.

    – Scott Arciszewski
    Jul 14 '15 at 22:47














83












83








83


71






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.










share|improve this question
















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.







php security random timestamp token






share|improve this question















share|improve this question













share|improve this question




share|improve this question








edited Dec 20 '15 at 6:45









Scott Arciszewski

23k1057155




23k1057155










asked Sep 20 '13 at 7:06









keenkeen

1,53732154




1,53732154













  • @AlmaDoMundo: A computer can't divide time unlimited.

    – juergen d
    Sep 20 '13 at 7:15











  • @juergend - sorry, do not get that.

    – Alma Do
    Sep 20 '13 at 7:15











  • You will get the same timestamp if you call it for instance a nano second apart. Some time functions for instance can only return time in 100ns steps, some only in seconds step.

    – juergen d
    Sep 20 '13 at 7:17













  • @juergend ah, that. Yes. I mentioned 'classic' timestamp with seconds only. But if act like you've said - yes (that only leaves us an option with time machine to get non-unique timestamp)

    – Alma Do
    Sep 20 '13 at 7:18






  • 1





    Head's up, the accepted answer does not leverage a CSPRNG.

    – Scott Arciszewski
    Jul 14 '15 at 22:47



















  • @AlmaDoMundo: A computer can't divide time unlimited.

    – juergen d
    Sep 20 '13 at 7:15











  • @juergend - sorry, do not get that.

    – Alma Do
    Sep 20 '13 at 7:15











  • You will get the same timestamp if you call it for instance a nano second apart. Some time functions for instance can only return time in 100ns steps, some only in seconds step.

    – juergen d
    Sep 20 '13 at 7:17













  • @juergend ah, that. Yes. I mentioned 'classic' timestamp with seconds only. But if act like you've said - yes (that only leaves us an option with time machine to get non-unique timestamp)

    – Alma Do
    Sep 20 '13 at 7:18






  • 1





    Head's up, the accepted answer does not leverage a CSPRNG.

    – Scott Arciszewski
    Jul 14 '15 at 22:47

















@AlmaDoMundo: A computer can't divide time unlimited.

– juergen d
Sep 20 '13 at 7:15





@AlmaDoMundo: A computer can't divide time unlimited.

– juergen d
Sep 20 '13 at 7:15













@juergend - sorry, do not get that.

– Alma Do
Sep 20 '13 at 7:15





@juergend - sorry, do not get that.

– Alma Do
Sep 20 '13 at 7:15













You will get the same timestamp if you call it for instance a nano second apart. Some time functions for instance can only return time in 100ns steps, some only in seconds step.

– juergen d
Sep 20 '13 at 7:17







You will get the same timestamp if you call it for instance a nano second apart. Some time functions for instance can only return time in 100ns steps, some only in seconds step.

– juergen d
Sep 20 '13 at 7:17















@juergend ah, that. Yes. I mentioned 'classic' timestamp with seconds only. But if act like you've said - yes (that only leaves us an option with time machine to get non-unique timestamp)

– Alma Do
Sep 20 '13 at 7:18





@juergend ah, that. Yes. I mentioned 'classic' timestamp with seconds only. But if act like you've said - yes (that only leaves us an option with time machine to get non-unique timestamp)

– Alma Do
Sep 20 '13 at 7:18




1




1





Head's up, the accepted answer does not leverage a CSPRNG.

– Scott Arciszewski
Jul 14 '15 at 22:47





Head's up, the accepted answer does not leverage a CSPRNG.

– Scott Arciszewski
Jul 14 '15 at 22:47












6 Answers
6






active

oldest

votes


















129














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






share|improve this answer





















  • 17





    Note that random_bytes() is only available as of PHP7. For older versions, the answer by @yesitsme seems to be the best option.

    – Gerald Schneider
    Jul 26 '15 at 13:36






  • 3





    @GeraldSchneider or random_compat, which is the polyfill for these features that has received the most peer review ;)

    – Scott Arciszewski
    Aug 13 '15 at 15:36











  • I made a varchar(64) field in my sql database to store this token. I set $length to 64, but the string returned is 128 characters long. How can I get a string with a fixed size (here, 64 then) ?

    – gordie
    Feb 22 '16 at 19:12






  • 2





    @gordie Set the length to 32, each byte is 2 hex characters

    – JohnHoulderUK
    Jun 21 '16 at 10:05











  • What should be $length ? The id of user? Or what?

    – stack
    Jun 24 '16 at 14:41



















63














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/






share|improve this answer





















  • 19





    openssl_random_pseudo_bytes($length) - support: PHP 5 >= 5.3.0 , .......................................................... (For PHP 7 and up, use random_bytes($length)) .......................................... (For PHP below 5.3 - don't use PHP below 5.3)

    – jave.web
    Jan 3 '16 at 22:46













  • For php 5.3 to php7 use github.com/paragonie/random_compat

    – dsas
    Dec 21 '16 at 10:59



















52














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-dTH: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.






share|improve this answer


























  • When you verify the user-provided reset token, why do you use the binary representation of the random token? Do you think it will be possible (and secure?) to: 1) store in DB the hashed hex value of the token with hash('sha256', bin2hex($token)), 2) verify with if (hash_equals(hash('sha256', $validator), $results[0]['token'])) {...? Thanks!

    – Guicara
    Dec 18 '15 at 16:25











  • Yes, comparing hex strings is secure too. It's really a matter of preference. I prefer to do all crypto operations on raw binary and only ever convert to hex/base64 for transmission or storage.

    – Scott Arciszewski
    Dec 18 '15 at 18:56











  • Hi Scott, it is basically a question not only for your answer, but for the entire article about the "Remember Me" feature. Why not to use the unique id as the selector? I mean, the primary key of the account_recovery table. We don't need additional layer of security for the selector, do we? Thanks!

    – Andre Polykanine
    May 28 '17 at 21:31











  • id:secret is OK. selector:secret is OK. secret itself is not. The goal is to separate the database query (which is timing-leaky) from the authentication protocol (which should be constant time).

    – Scott Arciszewski
    May 29 '17 at 5:50











  • Is there any harm in using openssl_random_pseudo_bytes instead random_bytes if is running PHP 5.6? Also, shouldn't you append just the selector and not the validator in the querystring of the link?

    – greg
    Jul 11 '18 at 14:09



















6














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





share|improve this answer





















  • 3





    I would suggest MCRYPT_DEV_URANDOM over MCRYPT_DEV_RANDOM.

    – Scott Arciszewski
    Jul 14 '15 at 22:45



















1














This may be helpful whenever you need a very very random token



<?php
echo mb_strtoupper(strval(bin2hex(openssl_random_pseudo_bytes(16))));
?>





share|improve this answer

































    -6














    You can use



    echo str_shuffle('ASGDHFfdgfdre5475433fd');





    share|improve this answer


























    • Hahaha yes this!

      – Jeff Puckett
      Feb 10 '17 at 14:41











    Your Answer






    StackExchange.ifUsing("editor", function () {
    StackExchange.using("externalEditor", function () {
    StackExchange.using("snippets", function () {
    StackExchange.snippets.init();
    });
    });
    }, "code-snippets");

    StackExchange.ready(function() {
    var channelOptions = {
    tags: "".split(" "),
    id: "1"
    };
    initTagRenderer("".split(" "), "".split(" "), channelOptions);

    StackExchange.using("externalEditor", function() {
    // Have to fire editor after snippets, if snippets enabled
    if (StackExchange.settings.snippets.snippetsEnabled) {
    StackExchange.using("snippets", function() {
    createEditor();
    });
    }
    else {
    createEditor();
    }
    });

    function createEditor() {
    StackExchange.prepareEditor({
    heartbeatType: 'answer',
    autoActivateHeartbeat: false,
    convertImagesToLinks: true,
    noModals: true,
    showLowRepImageUploadWarning: true,
    reputationToPostImages: 10,
    bindNavPrevention: true,
    postfix: "",
    imageUploader: {
    brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
    contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
    allowUrls: true
    },
    onDemand: true,
    discardSelector: ".discard-answer"
    ,immediatelyShowMarkdownHelp:true
    });


    }
    });














    draft saved

    draft discarded


















    StackExchange.ready(
    function () {
    StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f18910814%2fbest-practice-to-generate-random-token-for-forgot-password%23new-answer', 'question_page');
    }
    );

    Post as a guest















    Required, but never shown

























    6 Answers
    6






    active

    oldest

    votes








    6 Answers
    6






    active

    oldest

    votes









    active

    oldest

    votes






    active

    oldest

    votes









    129














    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






    share|improve this answer





















    • 17





      Note that random_bytes() is only available as of PHP7. For older versions, the answer by @yesitsme seems to be the best option.

      – Gerald Schneider
      Jul 26 '15 at 13:36






    • 3





      @GeraldSchneider or random_compat, which is the polyfill for these features that has received the most peer review ;)

      – Scott Arciszewski
      Aug 13 '15 at 15:36











    • I made a varchar(64) field in my sql database to store this token. I set $length to 64, but the string returned is 128 characters long. How can I get a string with a fixed size (here, 64 then) ?

      – gordie
      Feb 22 '16 at 19:12






    • 2





      @gordie Set the length to 32, each byte is 2 hex characters

      – JohnHoulderUK
      Jun 21 '16 at 10:05











    • What should be $length ? The id of user? Or what?

      – stack
      Jun 24 '16 at 14:41
















    129














    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






    share|improve this answer





















    • 17





      Note that random_bytes() is only available as of PHP7. For older versions, the answer by @yesitsme seems to be the best option.

      – Gerald Schneider
      Jul 26 '15 at 13:36






    • 3





      @GeraldSchneider or random_compat, which is the polyfill for these features that has received the most peer review ;)

      – Scott Arciszewski
      Aug 13 '15 at 15:36











    • I made a varchar(64) field in my sql database to store this token. I set $length to 64, but the string returned is 128 characters long. How can I get a string with a fixed size (here, 64 then) ?

      – gordie
      Feb 22 '16 at 19:12






    • 2





      @gordie Set the length to 32, each byte is 2 hex characters

      – JohnHoulderUK
      Jun 21 '16 at 10:05











    • What should be $length ? The id of user? Or what?

      – stack
      Jun 24 '16 at 14:41














    129












    129








    129







    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






    share|improve this answer















    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







    share|improve this answer














    share|improve this answer



    share|improve this answer








    edited May 23 '17 at 11:33









    Community

    11




    11










    answered Sep 20 '13 at 7:14









    Alma DoAlma Do

    31.5k75288




    31.5k75288








    • 17





      Note that random_bytes() is only available as of PHP7. For older versions, the answer by @yesitsme seems to be the best option.

      – Gerald Schneider
      Jul 26 '15 at 13:36






    • 3





      @GeraldSchneider or random_compat, which is the polyfill for these features that has received the most peer review ;)

      – Scott Arciszewski
      Aug 13 '15 at 15:36











    • I made a varchar(64) field in my sql database to store this token. I set $length to 64, but the string returned is 128 characters long. How can I get a string with a fixed size (here, 64 then) ?

      – gordie
      Feb 22 '16 at 19:12






    • 2





      @gordie Set the length to 32, each byte is 2 hex characters

      – JohnHoulderUK
      Jun 21 '16 at 10:05











    • What should be $length ? The id of user? Or what?

      – stack
      Jun 24 '16 at 14:41














    • 17





      Note that random_bytes() is only available as of PHP7. For older versions, the answer by @yesitsme seems to be the best option.

      – Gerald Schneider
      Jul 26 '15 at 13:36






    • 3





      @GeraldSchneider or random_compat, which is the polyfill for these features that has received the most peer review ;)

      – Scott Arciszewski
      Aug 13 '15 at 15:36











    • I made a varchar(64) field in my sql database to store this token. I set $length to 64, but the string returned is 128 characters long. How can I get a string with a fixed size (here, 64 then) ?

      – gordie
      Feb 22 '16 at 19:12






    • 2





      @gordie Set the length to 32, each byte is 2 hex characters

      – JohnHoulderUK
      Jun 21 '16 at 10:05











    • What should be $length ? The id of user? Or what?

      – stack
      Jun 24 '16 at 14:41








    17




    17





    Note that random_bytes() is only available as of PHP7. For older versions, the answer by @yesitsme seems to be the best option.

    – Gerald Schneider
    Jul 26 '15 at 13:36





    Note that random_bytes() is only available as of PHP7. For older versions, the answer by @yesitsme seems to be the best option.

    – Gerald Schneider
    Jul 26 '15 at 13:36




    3




    3





    @GeraldSchneider or random_compat, which is the polyfill for these features that has received the most peer review ;)

    – Scott Arciszewski
    Aug 13 '15 at 15:36





    @GeraldSchneider or random_compat, which is the polyfill for these features that has received the most peer review ;)

    – Scott Arciszewski
    Aug 13 '15 at 15:36













    I made a varchar(64) field in my sql database to store this token. I set $length to 64, but the string returned is 128 characters long. How can I get a string with a fixed size (here, 64 then) ?

    – gordie
    Feb 22 '16 at 19:12





    I made a varchar(64) field in my sql database to store this token. I set $length to 64, but the string returned is 128 characters long. How can I get a string with a fixed size (here, 64 then) ?

    – gordie
    Feb 22 '16 at 19:12




    2




    2





    @gordie Set the length to 32, each byte is 2 hex characters

    – JohnHoulderUK
    Jun 21 '16 at 10:05





    @gordie Set the length to 32, each byte is 2 hex characters

    – JohnHoulderUK
    Jun 21 '16 at 10:05













    What should be $length ? The id of user? Or what?

    – stack
    Jun 24 '16 at 14:41





    What should be $length ? The id of user? Or what?

    – stack
    Jun 24 '16 at 14:41













    63














    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/






    share|improve this answer





















    • 19





      openssl_random_pseudo_bytes($length) - support: PHP 5 >= 5.3.0 , .......................................................... (For PHP 7 and up, use random_bytes($length)) .......................................... (For PHP below 5.3 - don't use PHP below 5.3)

      – jave.web
      Jan 3 '16 at 22:46













    • For php 5.3 to php7 use github.com/paragonie/random_compat

      – dsas
      Dec 21 '16 at 10:59
















    63














    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/






    share|improve this answer





















    • 19





      openssl_random_pseudo_bytes($length) - support: PHP 5 >= 5.3.0 , .......................................................... (For PHP 7 and up, use random_bytes($length)) .......................................... (For PHP below 5.3 - don't use PHP below 5.3)

      – jave.web
      Jan 3 '16 at 22:46













    • For php 5.3 to php7 use github.com/paragonie/random_compat

      – dsas
      Dec 21 '16 at 10:59














    63












    63








    63







    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/






    share|improve this answer















    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/







    share|improve this answer














    share|improve this answer



    share|improve this answer








    edited Nov 14 '18 at 15:04









    TylerH

    15.8k105368




    15.8k105368










    answered Mar 19 '15 at 5:17









    yesitsmeyesitsme

    1,204924




    1,204924








    • 19





      openssl_random_pseudo_bytes($length) - support: PHP 5 >= 5.3.0 , .......................................................... (For PHP 7 and up, use random_bytes($length)) .......................................... (For PHP below 5.3 - don't use PHP below 5.3)

      – jave.web
      Jan 3 '16 at 22:46













    • For php 5.3 to php7 use github.com/paragonie/random_compat

      – dsas
      Dec 21 '16 at 10:59














    • 19





      openssl_random_pseudo_bytes($length) - support: PHP 5 >= 5.3.0 , .......................................................... (For PHP 7 and up, use random_bytes($length)) .......................................... (For PHP below 5.3 - don't use PHP below 5.3)

      – jave.web
      Jan 3 '16 at 22:46













    • For php 5.3 to php7 use github.com/paragonie/random_compat

      – dsas
      Dec 21 '16 at 10:59








    19




    19





    openssl_random_pseudo_bytes($length) - support: PHP 5 >= 5.3.0 , .......................................................... (For PHP 7 and up, use random_bytes($length)) .......................................... (For PHP below 5.3 - don't use PHP below 5.3)

    – jave.web
    Jan 3 '16 at 22:46







    openssl_random_pseudo_bytes($length) - support: PHP 5 >= 5.3.0 , .......................................................... (For PHP 7 and up, use random_bytes($length)) .......................................... (For PHP below 5.3 - don't use PHP below 5.3)

    – jave.web
    Jan 3 '16 at 22:46















    For php 5.3 to php7 use github.com/paragonie/random_compat

    – dsas
    Dec 21 '16 at 10:59





    For php 5.3 to php7 use github.com/paragonie/random_compat

    – dsas
    Dec 21 '16 at 10:59











    52














    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-dTH: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.






    share|improve this answer


























    • When you verify the user-provided reset token, why do you use the binary representation of the random token? Do you think it will be possible (and secure?) to: 1) store in DB the hashed hex value of the token with hash('sha256', bin2hex($token)), 2) verify with if (hash_equals(hash('sha256', $validator), $results[0]['token'])) {...? Thanks!

      – Guicara
      Dec 18 '15 at 16:25











    • Yes, comparing hex strings is secure too. It's really a matter of preference. I prefer to do all crypto operations on raw binary and only ever convert to hex/base64 for transmission or storage.

      – Scott Arciszewski
      Dec 18 '15 at 18:56











    • Hi Scott, it is basically a question not only for your answer, but for the entire article about the "Remember Me" feature. Why not to use the unique id as the selector? I mean, the primary key of the account_recovery table. We don't need additional layer of security for the selector, do we? Thanks!

      – Andre Polykanine
      May 28 '17 at 21:31











    • id:secret is OK. selector:secret is OK. secret itself is not. The goal is to separate the database query (which is timing-leaky) from the authentication protocol (which should be constant time).

      – Scott Arciszewski
      May 29 '17 at 5:50











    • Is there any harm in using openssl_random_pseudo_bytes instead random_bytes if is running PHP 5.6? Also, shouldn't you append just the selector and not the validator in the querystring of the link?

      – greg
      Jul 11 '18 at 14:09
















    52














    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-dTH: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.






    share|improve this answer


























    • When you verify the user-provided reset token, why do you use the binary representation of the random token? Do you think it will be possible (and secure?) to: 1) store in DB the hashed hex value of the token with hash('sha256', bin2hex($token)), 2) verify with if (hash_equals(hash('sha256', $validator), $results[0]['token'])) {...? Thanks!

      – Guicara
      Dec 18 '15 at 16:25











    • Yes, comparing hex strings is secure too. It's really a matter of preference. I prefer to do all crypto operations on raw binary and only ever convert to hex/base64 for transmission or storage.

      – Scott Arciszewski
      Dec 18 '15 at 18:56











    • Hi Scott, it is basically a question not only for your answer, but for the entire article about the "Remember Me" feature. Why not to use the unique id as the selector? I mean, the primary key of the account_recovery table. We don't need additional layer of security for the selector, do we? Thanks!

      – Andre Polykanine
      May 28 '17 at 21:31











    • id:secret is OK. selector:secret is OK. secret itself is not. The goal is to separate the database query (which is timing-leaky) from the authentication protocol (which should be constant time).

      – Scott Arciszewski
      May 29 '17 at 5:50











    • Is there any harm in using openssl_random_pseudo_bytes instead random_bytes if is running PHP 5.6? Also, shouldn't you append just the selector and not the validator in the querystring of the link?

      – greg
      Jul 11 '18 at 14:09














    52












    52








    52







    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-dTH: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.






    share|improve this answer















    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-dTH: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.







    share|improve this answer














    share|improve this answer



    share|improve this answer








    edited Jun 26 '16 at 4:38

























    answered Jul 14 '15 at 23:47









    Scott ArciszewskiScott Arciszewski

    23k1057155




    23k1057155













    • When you verify the user-provided reset token, why do you use the binary representation of the random token? Do you think it will be possible (and secure?) to: 1) store in DB the hashed hex value of the token with hash('sha256', bin2hex($token)), 2) verify with if (hash_equals(hash('sha256', $validator), $results[0]['token'])) {...? Thanks!

      – Guicara
      Dec 18 '15 at 16:25











    • Yes, comparing hex strings is secure too. It's really a matter of preference. I prefer to do all crypto operations on raw binary and only ever convert to hex/base64 for transmission or storage.

      – Scott Arciszewski
      Dec 18 '15 at 18:56











    • Hi Scott, it is basically a question not only for your answer, but for the entire article about the "Remember Me" feature. Why not to use the unique id as the selector? I mean, the primary key of the account_recovery table. We don't need additional layer of security for the selector, do we? Thanks!

      – Andre Polykanine
      May 28 '17 at 21:31











    • id:secret is OK. selector:secret is OK. secret itself is not. The goal is to separate the database query (which is timing-leaky) from the authentication protocol (which should be constant time).

      – Scott Arciszewski
      May 29 '17 at 5:50











    • Is there any harm in using openssl_random_pseudo_bytes instead random_bytes if is running PHP 5.6? Also, shouldn't you append just the selector and not the validator in the querystring of the link?

      – greg
      Jul 11 '18 at 14:09



















    • When you verify the user-provided reset token, why do you use the binary representation of the random token? Do you think it will be possible (and secure?) to: 1) store in DB the hashed hex value of the token with hash('sha256', bin2hex($token)), 2) verify with if (hash_equals(hash('sha256', $validator), $results[0]['token'])) {...? Thanks!

      – Guicara
      Dec 18 '15 at 16:25











    • Yes, comparing hex strings is secure too. It's really a matter of preference. I prefer to do all crypto operations on raw binary and only ever convert to hex/base64 for transmission or storage.

      – Scott Arciszewski
      Dec 18 '15 at 18:56











    • Hi Scott, it is basically a question not only for your answer, but for the entire article about the "Remember Me" feature. Why not to use the unique id as the selector? I mean, the primary key of the account_recovery table. We don't need additional layer of security for the selector, do we? Thanks!

      – Andre Polykanine
      May 28 '17 at 21:31











    • id:secret is OK. selector:secret is OK. secret itself is not. The goal is to separate the database query (which is timing-leaky) from the authentication protocol (which should be constant time).

      – Scott Arciszewski
      May 29 '17 at 5:50











    • Is there any harm in using openssl_random_pseudo_bytes instead random_bytes if is running PHP 5.6? Also, shouldn't you append just the selector and not the validator in the querystring of the link?

      – greg
      Jul 11 '18 at 14:09

















    When you verify the user-provided reset token, why do you use the binary representation of the random token? Do you think it will be possible (and secure?) to: 1) store in DB the hashed hex value of the token with hash('sha256', bin2hex($token)), 2) verify with if (hash_equals(hash('sha256', $validator), $results[0]['token'])) {...? Thanks!

    – Guicara
    Dec 18 '15 at 16:25





    When you verify the user-provided reset token, why do you use the binary representation of the random token? Do you think it will be possible (and secure?) to: 1) store in DB the hashed hex value of the token with hash('sha256', bin2hex($token)), 2) verify with if (hash_equals(hash('sha256', $validator), $results[0]['token'])) {...? Thanks!

    – Guicara
    Dec 18 '15 at 16:25













    Yes, comparing hex strings is secure too. It's really a matter of preference. I prefer to do all crypto operations on raw binary and only ever convert to hex/base64 for transmission or storage.

    – Scott Arciszewski
    Dec 18 '15 at 18:56





    Yes, comparing hex strings is secure too. It's really a matter of preference. I prefer to do all crypto operations on raw binary and only ever convert to hex/base64 for transmission or storage.

    – Scott Arciszewski
    Dec 18 '15 at 18:56













    Hi Scott, it is basically a question not only for your answer, but for the entire article about the "Remember Me" feature. Why not to use the unique id as the selector? I mean, the primary key of the account_recovery table. We don't need additional layer of security for the selector, do we? Thanks!

    – Andre Polykanine
    May 28 '17 at 21:31





    Hi Scott, it is basically a question not only for your answer, but for the entire article about the "Remember Me" feature. Why not to use the unique id as the selector? I mean, the primary key of the account_recovery table. We don't need additional layer of security for the selector, do we? Thanks!

    – Andre Polykanine
    May 28 '17 at 21:31













    id:secret is OK. selector:secret is OK. secret itself is not. The goal is to separate the database query (which is timing-leaky) from the authentication protocol (which should be constant time).

    – Scott Arciszewski
    May 29 '17 at 5:50





    id:secret is OK. selector:secret is OK. secret itself is not. The goal is to separate the database query (which is timing-leaky) from the authentication protocol (which should be constant time).

    – Scott Arciszewski
    May 29 '17 at 5:50













    Is there any harm in using openssl_random_pseudo_bytes instead random_bytes if is running PHP 5.6? Also, shouldn't you append just the selector and not the validator in the querystring of the link?

    – greg
    Jul 11 '18 at 14:09





    Is there any harm in using openssl_random_pseudo_bytes instead random_bytes if is running PHP 5.6? Also, shouldn't you append just the selector and not the validator in the querystring of the link?

    – greg
    Jul 11 '18 at 14:09











    6














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





    share|improve this answer





















    • 3





      I would suggest MCRYPT_DEV_URANDOM over MCRYPT_DEV_RANDOM.

      – Scott Arciszewski
      Jul 14 '15 at 22:45
















    6














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





    share|improve this answer





















    • 3





      I would suggest MCRYPT_DEV_URANDOM over MCRYPT_DEV_RANDOM.

      – Scott Arciszewski
      Jul 14 '15 at 22:45














    6












    6








    6







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





    share|improve this answer















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






    share|improve this answer














    share|improve this answer



    share|improve this answer








    edited Nov 21 '13 at 19:36

























    answered Nov 21 '13 at 18:13









    Graham TGraham T

    777513




    777513








    • 3





      I would suggest MCRYPT_DEV_URANDOM over MCRYPT_DEV_RANDOM.

      – Scott Arciszewski
      Jul 14 '15 at 22:45














    • 3





      I would suggest MCRYPT_DEV_URANDOM over MCRYPT_DEV_RANDOM.

      – Scott Arciszewski
      Jul 14 '15 at 22:45








    3




    3





    I would suggest MCRYPT_DEV_URANDOM over MCRYPT_DEV_RANDOM.

    – Scott Arciszewski
    Jul 14 '15 at 22:45





    I would suggest MCRYPT_DEV_URANDOM over MCRYPT_DEV_RANDOM.

    – Scott Arciszewski
    Jul 14 '15 at 22:45











    1














    This may be helpful whenever you need a very very random token



    <?php
    echo mb_strtoupper(strval(bin2hex(openssl_random_pseudo_bytes(16))));
    ?>





    share|improve this answer






























      1














      This may be helpful whenever you need a very very random token



      <?php
      echo mb_strtoupper(strval(bin2hex(openssl_random_pseudo_bytes(16))));
      ?>





      share|improve this answer




























        1












        1








        1







        This may be helpful whenever you need a very very random token



        <?php
        echo mb_strtoupper(strval(bin2hex(openssl_random_pseudo_bytes(16))));
        ?>





        share|improve this answer















        This may be helpful whenever you need a very very random token



        <?php
        echo mb_strtoupper(strval(bin2hex(openssl_random_pseudo_bytes(16))));
        ?>






        share|improve this answer














        share|improve this answer



        share|improve this answer








        edited Feb 6 at 16:38









        A-B-B

        23.4k66369




        23.4k66369










        answered May 22 '17 at 11:22









        Ir CalifIr Calif

        14226




        14226























            -6














            You can use



            echo str_shuffle('ASGDHFfdgfdre5475433fd');





            share|improve this answer


























            • Hahaha yes this!

              – Jeff Puckett
              Feb 10 '17 at 14:41
















            -6














            You can use



            echo str_shuffle('ASGDHFfdgfdre5475433fd');





            share|improve this answer


























            • Hahaha yes this!

              – Jeff Puckett
              Feb 10 '17 at 14:41














            -6












            -6








            -6







            You can use



            echo str_shuffle('ASGDHFfdgfdre5475433fd');





            share|improve this answer















            You can use



            echo str_shuffle('ASGDHFfdgfdre5475433fd');






            share|improve this answer














            share|improve this answer



            share|improve this answer








            edited Aug 8 '18 at 0:20









            Pang

            6,9091664102




            6,9091664102










            answered Oct 8 '15 at 22:47









            saifsaif

            111




            111













            • Hahaha yes this!

              – Jeff Puckett
              Feb 10 '17 at 14:41



















            • Hahaha yes this!

              – Jeff Puckett
              Feb 10 '17 at 14:41

















            Hahaha yes this!

            – Jeff Puckett
            Feb 10 '17 at 14:41





            Hahaha yes this!

            – Jeff Puckett
            Feb 10 '17 at 14:41


















            draft saved

            draft discarded




















































            Thanks for contributing an answer to Stack Overflow!


            • Please be sure to answer the question. Provide details and share your research!

            But avoid



            • Asking for help, clarification, or responding to other answers.

            • Making statements based on opinion; back them up with references or personal experience.


            To learn more, see our tips on writing great answers.




            draft saved


            draft discarded














            StackExchange.ready(
            function () {
            StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f18910814%2fbest-practice-to-generate-random-token-for-forgot-password%23new-answer', 'question_page');
            }
            );

            Post as a guest















            Required, but never shown





















































            Required, but never shown














            Required, but never shown












            Required, but never shown







            Required, but never shown

































            Required, but never shown














            Required, but never shown












            Required, but never shown







            Required, but never shown







            Popular posts from this blog

            Florida Star v. B. J. F.

            Error while running script in elastic search , gateway timeout

            Adding quotations to stringified JSON object values