34

I want to create a token generator that generates tokens that cannot be guessed by the user and that are still unique (to be used for password resets and confirmation codes).

I often see this code; does it make sense?

md5(uniqid(rand(), true));

According to a comment uniqid($prefix, $moreEntopy = true) yields

first 8 hex chars = Unixtime, last 5 hex chars = microseconds.

I don't know how the $prefix-parameter is handled..

So if you don't set the $moreEntopy flag to true, it gives a predictable outcome.


QUESTION: But if we use uniqid with $moreEntopy, what does hashing it with md5 buy us? Is it better than:

md5(mt_rand())

edit1: I will store this token in an database column with a unique index, so I will detect columns. Might be of interest/

Exception e
  • 1,864
  • 3
  • 19
  • 33

8 Answers8

44

rand() is a security hazard and should never be used to generate a security token: rand() vs mt_rand() (Look at the "static" like images). But neither of these methods of generating random numbers is cryptographically secure. To generate secure secerts an application will needs to access a CSPRNG provided by the platform, operating system or hardware module.

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;
}
Community
  • 1
  • 1
rook
  • 66,304
  • 38
  • 162
  • 239
  • 1
    `md5() in this case is being used to obscure the time that it was created which is a legitimate use` That sounds like the answer. – Exception e Apr 07 '10 at 23:10
  • i have a server process that needs to take form data and save it as uniquetoken.xml. I already have the form ready to create the xml file. So far I'm naming them with each mmddyy.xml. How could I replace it with md5(uniqid(mt_rand(), true)); ? – marciokoko Jan 06 '12 at 15:44
  • 1
    This is insecure. See Scott's answer. – ChocoDeveloper Aug 12 '13 at 14:15
  • @ChocoDeveloper "/dev/urandom is by far the best source of entropy for a web application." – rook Aug 12 '13 at 16:17
  • I'm talking about the snippet of code you provided and labeled as "for most security purposes this is a good token", ready to be used by noobs. Besides, /dev/urandom isn't "the best source of entropy" either, since it's non-blocking. There is a reason why `openssl_random_pseudo_bytes()` exists. – ChocoDeveloper Aug 12 '13 at 16:27
  • @ChocoDeveloper openssl will use /dev/urandom on a *nix box, look it up! Using a blocking operation on a webapp is "noob". – rook Aug 12 '13 at 19:03
21

This is a copy of another question I found that was asked a few months before this one. Here is a link to the question and my answer: https://stackoverflow.com/a/13733588/1698153.

I do not agree with the accepted answer. According to PHPs own website "[uniqid] does not generate cryptographically secure tokens, in fact without being passed any additional parameters the return value is little different from microtime(). If you need to generate cryptographically secure tokens use openssl_random_pseudo_bytes()."

I do not think the answer could be clearer than this, uniqid is not secure.

Community
  • 1
  • 1
Scott
  • 12,077
  • 4
  • 27
  • 48
  • upvoted so people should really checkout the link instead of the accepted answer. – perlwle Sep 09 '13 at 02:10
  • 1
    Scott, I wasn't aware of that older question, sry. I guess `openssl_random_pseudo_bytes()` (PHP 5 >= php 5.3) wasn't always available at the time of asking. You are right, the accepted answer has been changed and refers to your answer at this moment, so other people won't be misguided. – Exception e Apr 26 '14 at 11:34
7

I know the question is old, but it shows up in Google, so...

As others said, rand(), mt_rand() or uniqid() will not guarantee you uniqueness... even openssl_random_pseudo_bytes() should not be used, since it uses deprecated features of OpenSSL.

What you should use to generate random hash (same as md5) is random_bytes() (introduced in PHP7). To generate hash with same length as MD5:

bin2hex(random_bytes(16));

If you are using PHP 5.x you can get this function by including random_compat library.

Jarek Jakubowski
  • 948
  • 10
  • 22
  • Valid answer! Your concern regarding `openssl_random_pseudo_bytes()` seems to have been fixed https://bugs.php.net/bug.php?id=70014. – Exception e Feb 08 '17 at 10:08
1

Define "unique". If you mean that two tokens cannot have the same value, then hashing isn't enough - it should be backed with a uniqueness test. The fact that you supply the hash algorithm with unique inputs does not guarantee unique outputs.

M.A. Hanin
  • 8,044
  • 33
  • 51
  • unique = don't have the same value indeed. Each token is associated with a unique e-mail address. Maybe we could use this to make a random token unique. I am still unsure what the best way to do this. – Exception e Apr 07 '10 at 15:54
1

To answer your question, the problem is you can't have a generator that is guaranteed random and unique as random by itself, i.e., md5(mt_rand()) can lead to duplicates. What you want is "random appearing" unique values. uniqid gives the unique id, rand() affixes a random number making it even harder to guess, md5 masks the result to make it yet even harder to guess. Nothing is unguessable. We just need to make it so hard that they wouldn't even want to try.

webbiedave
  • 48,414
  • 8
  • 88
  • 101
  • 1
    You say "md5 masks the result to make it yet even harder to guess". So uniqueid generates a somewhat random result that is unique. What do you precisely mean by masking? If I would follow this thought than `md5(md5(uniqid(rand(), true)` would mean even more masking, but it is worse right? – Exception e Apr 07 '10 at 17:10
  • @Excpetion e, a double md5() hash doesn't improve the secuirty of this system. – rook Apr 07 '10 at 19:42
1

I ran into an interesting idea a couple of years ago.
Storing two hash values in the datebase, one generated with md5($a) and the other with sha($a). Then chek if both the values are corect. Point is, if the attacker broke your md5(), he cannot break your md5 AND sha in the near future.
Problem is: how can that concept be used with the token generating needed for your problem?

Mr47
  • 2,655
  • 1
  • 19
  • 25
gogink
  • 11
  • 1
  • Wouldnt just combining them with a string glue like _ or - be enough to create a merged token which would be much more unlikely to have a collusion ? You dont need to keep them in separate db fields. You can keep them as merged. And if you need to do any operations on them, you can just process the string with a function. This way the combined string can be used as a token for any unique string requiring process, like session keys or password reset requests or many other things. – unity100 Nov 02 '12 at 17:37
0

First, the scope of this kind of procedure is to create a key/hash/code, that will be unique for one given database. It is impossible to create something unique for the whole world at a given moment. That being said, you should create a plain, visible string, using a custom alphabet, and checking the created code against your database (table). If that string is unique, then you apply a md5() to it and that can't be guessed by anyone or any script. I know that if you dig deep into the theory of cryptographic generation you can find a lot of explanation about this kind of code generation, but when you put it to real usage it's really not that complicated.

Here's the code I use to generate a simple 10 digit unique code.

$alphabet = "aA1!bB2@cC3#dD5%eE6^fF7&gG8*hH9(iI0)jJ4-kK=+lL[mM]nN{oO}pP\qQ/rR,sS.tT?uUvV>xX~yY|zZ`wW$";
$code = '';
$alplhaLenght = strlen($alphabet )-1;
for ($i = 1; $i <= 10; $i++) {
    $n = rand(1, $alplhaLenght );
    $code .= $alphabet [$n];
}

And here are some generated codes, although you can run it yourself to see it work:

SpQ0T0tyO%
Uwn[MU][.
D|[ROt+Cd@
O6I|w38TRe

Of course, there can be a lot of "improvements" that can be applied to it, to make it more "complicated", but if you apply a md5() to this, it'll become, let's say "unguessable" . :)

Lucian Minea
  • 1,300
  • 10
  • 12
-1

MD5 is a decent algorithm for producing data dependent IDs. But in case you have more than one item which has the same bitstream (content), you will be producing two similar MD5 "ids".

So if you are just applying it to a rand() function, which is guaranteed not to create the same number twice, you are quite safe.

But for a stronger distribution of keys, I'd personally use SHA1 or SHAx etc'... but you will still have the problem of similar data leads to similar keys.

Etamar Laron
  • 1,172
  • 10
  • 23