Reputation: 1701
I have an interesting example where multiple, repeated calls to uniqid()
are not generating unique numbers when hosted locally on XAMPP. The unique id is repeated anywhere between 5-20 times and, then, mysteriously changes.
However, as an interesting twist, the code works perfectly on our production server.
So here's what I'm doing: I'm creating a wrapper that, when clicked, hides/unhides the child content in a div via a simple javascript function. Since the hideable div is being generated dynamically, it is referenced by a unique id that is generated by PHP.
An example of the issue is as follows:
// Replace something like '[element] => <newline> (' with <a href="javascript:toggleDisplay('[unique id]');">...</a><div id="[unique id]" style="display: none;">
$out = preg_replace_callback(
$regex,
function ($matches) {
$id = uniqid();
return $matches[1] . "<a class='debug' href='javascript:toggleDisplay(\"" . $id . "\");'>" . $matches[2] . "</a>" . "<div id='" . $id . "' style='display: none'>";
}, $out
);
The javascript function is as follows (just so you can see what I'm doing; it works perfectly):
<script language="Javascript">
function toggleDisplay(id) {
document.getElementById(id).style.display = (document.getElementById(id).style.display == "block") ? "none" : "block";
}
</script>'
The problem is that the output divs all have the same unique id (!!), in clusters anywhere between 5-15, so the javascript doesn't know what div to reference.
So some things I have found: If I do something like $id = uniqid() . rand(10000,99999)
instead of just $id = uniqid()
then the code works again as intended. So I'm pretty sure that the problem is that uniqid()
is not actually generating a unique id, considering that I'm not overwriting or reusing the $id
variable.
Another interesting thing I have found: if I echo the microtime()
along with the uniqid()
, the uniqid()
changes only when the microtime()
changes. To me this feels like a clue.
So my question is: Why would uniqid()
only sometimes generate a uniqid()
? Isn't uniqid()
supposed to generate a unique number, even if the microtime()
is the same? Is this behavior documented or well known? Or is there something else that I'm missing?
I ask because I'm feeling uncomfortable using uniqid()
because I'm not understanding the core behavior.
Any insight would be appreciated. Thank you.
Upvotes: 6
Views: 7232
Reputation: 98005
The result of uniqid()
is not guaranteed to be unique, and your investigation with microtime()
is indeed a clue as to why.
According to the manual page for uniqid()
, it:
Gets a prefixed unique identifier based on the current time in microseconds.
So the main input is indeed the current "microtime". However, it also takes an extra parameter:
more_entropy
If set to TRUE, uniqid() will add additional entropy (using the combined linear congruential generator) at the end of the return value, which increases the likelihood that the result will be unique.
Note that even with this argument, the manual is careful not to guarantee uniqueness, but as with your manual use of rand()
, it is adding an extra source of randomness which makes collisions vastly more unlikely.
To confirm, we can look at the source code for the function, where we can see that the output without more_entropy
set is indeed just a hex representation of the current microsecond timestamp. An interesting piece to notice is this:
#if HAVE_USLEEP && !defined(PHP_WIN32)
if (!more_entropy) {
#if defined(__CYGWIN__)
php_error_docref(NULL, E_WARNING, "You must use 'more entropy' under CYGWIN");
RETURN_FALSE;
#else
usleep(1);
#endif
}
#endif
So, if you're not under Windows, the function will actually try to sleep for a microsecond in order to force subsequent values to be different.
This makes it a bad idea to run uniqid()
lots of times in succession, because if it does succeed, it will do so slowly. (Requiring either a microsecond of sleep, or a call to the random-number generator.)
A better idea is to use it once to generate an arbitrary prefix, and then simply increment a counter for each item, which could look something like this:
$id_prefix = uniqid();
$id_suffix = 0;
$out = preg_replace_callback(
$regex,
function ($matches) use ($id_prefix, &$id_suffix) {
$id = $id_prefix . $id_suffix;
$id_suffix ++;
return $matches[1] . '... some html ...' . $id . ' ... ';
},
$out
);
Upvotes: 10
Reputation: 32923
From the documentation of uniqid()
Warning This function does not create random nor unpredictable strings. This function must not be used for security purposes. Use a cryptographically secure random function/generator and cryptographically secure hash functions to create unpredictable secure IDs.
If the OS you're running the script doesn't provide a very good clock resolution, then uniqid() might return the same value several times in a row.
You could have also used $id = uniqid(rand(10000,99999))
, or to further increase the randomness chances: $id = uniqid(rand(10000,99999), true)
.
Anyhow, the conclusion is that the name is misleading, as it doesn't guarantee that you'll get unique values each time the function is called.
Upvotes: 4