ChatGPT解决这个技术问题 Extra ChatGPT

PHP function to generate v4 UUID

So I've been doing some digging around and I've been trying to piece together a function that generates a valid v4 UUID in PHP. This is the closest I've been able to come. My knowledge in hex, decimal, binary, PHP's bitwise operators and the like is nearly nonexistent. This function generates a valid v4 UUID up until one area. A v4 UUID should be in the form of:

xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx

Where y is 8, 9, A, or B. This is where the functions fails as it doesn't adhere to that.

I was hoping someone with more knowledge than me in this area could lend me a hand and help me fix this function so it does adhere to that rule.

The function is as follows:

<?php

function gen_uuid() {
 $uuid = array(
  'time_low'  => 0,
  'time_mid'  => 0,
  'time_hi'  => 0,
  'clock_seq_hi' => 0,
  'clock_seq_low' => 0,
  'node'   => array()
 );
 
 $uuid['time_low'] = mt_rand(0, 0xffff) + (mt_rand(0, 0xffff) << 16);
 $uuid['time_mid'] = mt_rand(0, 0xffff);
 $uuid['time_hi'] = (4 << 12) | (mt_rand(0, 0x1000));
 $uuid['clock_seq_hi'] = (1 << 7) | (mt_rand(0, 128));
 $uuid['clock_seq_low'] = mt_rand(0, 255);
 
 for ($i = 0; $i < 6; $i++) {
  $uuid['node'][$i] = mt_rand(0, 255);
 }
 
 $uuid = sprintf('%08x-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x',
  $uuid['time_low'],
  $uuid['time_mid'],
  $uuid['time_hi'],
  $uuid['clock_seq_hi'],
  $uuid['clock_seq_low'],
  $uuid['node'][0],
  $uuid['node'][1],
  $uuid['node'][2],
  $uuid['node'][3],
  $uuid['node'][4],
  $uuid['node'][5]
 );
 
 return $uuid;
}

?>
If you are on Linux and if you are a little lazy you can generete them with $newId = exec('uuidgen -r');
You may consider using this library: github.com/abmmhasan/UUID then simply use the command: \AbmmHasan\Uuid::v4();

C
Community

Instead of breaking it down into individual fields, it's easier to generate a random block of data and change the individual byte positions. You should also use a better random number generator than mt_rand().

According to RFC 4122 - Section 4.4, you need to change these fields:

time_hi_and_version (bits 4-7 of 7th octet), clock_seq_hi_and_reserved (bit 6 & 7 of 9th octet)

All of the other 122 bits should be sufficiently random.

The following approach generates 128 bits of random data using openssl_random_pseudo_bytes(), makes the permutations on the octets and then uses bin2hex() and vsprintf() to do the final formatting.

function guidv4($data)
{
    assert(strlen($data) == 16);

    $data[6] = chr(ord($data[6]) & 0x0f | 0x40); // set version to 0100
    $data[8] = chr(ord($data[8]) & 0x3f | 0x80); // set bits 6-7 to 10

    return vsprintf('%s%s-%s-%s-%s-%s%s%s', str_split(bin2hex($data), 4));
}

echo guidv4(openssl_random_pseudo_bytes(16));

With PHP 7, generating random byte sequences is even simpler using random_bytes():

function guidv4($data = null)
{
    $data = $data ?? random_bytes(16);
    // ...
}

An alternative for *nix users who don't have the openssl extension: $data = file_get_contents('/dev/urandom', NULL, NULL, 0, 16);
Also, I would trust OpenSSL a lot more than mt_rand.
@BrunoAugusto it's random, and it's extremely unlikely (with a good random source) to get duplicates, but it's a good practice to enforce it at database level.
Is there any reason to NOT put the random_bytes(16) call inside the guidv4 function and thus not have to pass any parameter to guidv4?
Small improvement: Set a NULL default for $data, and then the first line of the function is this: $data = $data ?? random_bytes( 16 ); Now you CAN specify your own random data source, or let the function do it for you. :-)
W
William

Taken from this comment on the PHP manual, you could use this:

function gen_uuid() {
    return sprintf( '%04x%04x-%04x-%04x-%04x-%04x%04x%04x',
        // 32 bits for "time_low"
        mt_rand( 0, 0xffff ), mt_rand( 0, 0xffff ),

        // 16 bits for "time_mid"
        mt_rand( 0, 0xffff ),

        // 16 bits for "time_hi_and_version",
        // four most significant bits holds version number 4
        mt_rand( 0, 0x0fff ) | 0x4000,

        // 16 bits, 8 bits for "clk_seq_hi_res",
        // 8 bits for "clk_seq_low",
        // two most significant bits holds zero and one for variant DCE1.1
        mt_rand( 0, 0x3fff ) | 0x8000,

        // 48 bits for "node"
        mt_rand( 0, 0xffff ), mt_rand( 0, 0xffff ), mt_rand( 0, 0xffff )
    );
}

This function will create duplicates, so avoid it when you need unique values. Note that mt_rand() will always produce the same sequence of random numbers given the same seed. So every time a seed is repeated, the same exact UUID is generated. To get around this, you would need to seed it using time and mac address, but I'm not sure how you would do this, since mt_srand() requires an integer.
@PavlePredic mt_srand(crc32(serialize([microtime(true), 'USER_IP', 'ETC']))); (i'm another wiliam :P)
The PHP docs explicitly caution that mt_rand() does not generate cryptographically secure values. In other words, values generated by this function may be predictable. If you need to ensure that the UUIDs are not predictable, you should rather use Jack's solution below, which makes use of the openssl_random_pseudo_bytes() function.
what on earth is the point of generating a UUID if you fill every field with garbage?
PHP 7.0+ defines the function random_bytes() which will always generate cryptographically secure random bytes or throw an exception if it's unable to. This is better than even openssl_random_psuedo_bytes() whose output is sometimes not cryptographically secure under some circumstances.
d
djule5

Anyone using composer dependencies, you might want to consider this library: https://github.com/ramsey/uuid

It doesn't get any easier than this:

Uuid::uuid4();

Oh, I don't know.... Five lines of code vs. loading a library with dependencies? I prefer Jack's function. YMMV
+1 to Stephen. Ramsey uuid has a lot more functionality than just uuid4. I wan't a banana!, here you have the entire jungle!
UUID's aren't just random strings. There is a spec to how it works. To generate a proper random UUID that I don't have to worry about getting rejected later, I'd rather use a tested library than roll my own implementation.
It's a UUIDv4. It's (mostly, but for a few bits) random. This ain't cryptography. Paranoia against "rolling your own" is silly.
The overhead of using the library is non-existent, and it has tests. +1 for not reinventing the wheel.
T
ThorSummoner

on unix systems, use the system kernel to generate a uuid for you.

file_get_contents('/proc/sys/kernel/random/uuid')

Credit Samveen on https://serverfault.com/a/529319/210994

Note!: Using this method to get a uuid does in fact exhaust the entropy pool, very quickly! I would avoid using this where it would be called frequently.


Besides portability, note that the random source is /dev/random which blocks if the entropy pool is exhausted.
@Jack Would you kindly link some documentation on the topic of entropy pool exhaustion on unix systems please? I'd be interested to know more about a realistic use case where this method breaks down.
I was unable to find information on making this special kernel file source from /dev/urandom, which in my understanding wouldn't exhaust, but risks returning duplicate uuids. I guess its a tradeoff; do you really actually need a unique id influenced by system entropy?
I noticed once upon a time, that fetching a uuid via the linux kernel (a shared resource) was sufficient to guarantee unique uuids on the same system. I believe this procfs uuid is safe to use in that way. Be aware that there are multiple versions of UUID en.wikipedia.org/wiki/… and linux in general probably give you Version 3 and 5 types man7.org/linux/man-pages/man3/uuid_generate.3.html
These kind of solutions are really funny to me. Funny != bad
C
Cristián Carrasco
// php version >= 7
$uuid = vsprintf('%s%s-%s-%s-%s-%s%s%s', str_split(bin2hex(random_bytes(16)), 4));

Please add an explanation to your code to help others understand what it does.
this is what actually done by Symfony polyfil - github.com/symfony/polyfill-uuid/blob/master/Uuid.php#L320
Is this correct? A quick test returned c0a062b7-b225-c294-b8a0-06b98931a45b, which doesn't match with xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx. It returned a c instead of 4.
D
Danny Beckett

A slight variation on Jack's answer to add support for PHP < 7:

// Get an RFC-4122 compliant globaly unique identifier
function get_guid() {
    $data = PHP_MAJOR_VERSION < 7 ? openssl_random_pseudo_bytes(16) : random_bytes(16);
    $data[6] = chr(ord($data[6]) & 0x0f | 0x40);    // Set version to 0100
    $data[8] = chr(ord($data[8]) & 0x3f | 0x80);    // Set bits 6-7 to 10
    return vsprintf('%s%s-%s-%s-%s-%s%s%s', str_split(bin2hex($data), 4));
}

A
Arie

In my search for a creating a v4 uuid, I came first to this page, then found this on http://php.net/manual/en/function.com-create-guid.php

function guidv4()
{
    if (function_exists('com_create_guid') === true)
        return trim(com_create_guid(), '{}');

    $data = openssl_random_pseudo_bytes(16);
    $data[6] = chr(ord($data[6]) & 0x0f | 0x40); // set version to 0100
    $data[8] = chr(ord($data[8]) & 0x3f | 0x80); // set bits 6-7 to 10
    return vsprintf('%s%s-%s-%s-%s-%s%s%s', str_split(bin2hex($data), 4));
}

credit: pavel.volyntsev

Edit: to clarify, this function will always give you a v4 uuid (PHP >= 5.3.0).

When the com_create_guid function is available (usually only on Windows), it will use that and strip the curly braces.

If not present (Linux), it will fall back on this strong random openssl_random_pseudo_bytes function, it will then uses vsprintf to format it into v4 uuid.


2
2 revs

My answer is based on comment uniqid user comment but it uses openssl_random_pseudo_bytes function to generate random string instead of reading from /dev/urandom

function guid()
{
    $randomString = openssl_random_pseudo_bytes(16);
    $time_low = bin2hex(substr($randomString, 0, 4));
    $time_mid = bin2hex(substr($randomString, 4, 2));
    $time_hi_and_version = bin2hex(substr($randomString, 6, 2));
    $clock_seq_hi_and_reserved = bin2hex(substr($randomString, 8, 2));
    $node = bin2hex(substr($randomString, 10, 6));

    /**
     * Set the four most significant bits (bits 12 through 15) of the
     * time_hi_and_version field to the 4-bit version number from
     * Section 4.1.3.
     * @see http://tools.ietf.org/html/rfc4122#section-4.1.3
    */
    $time_hi_and_version = hexdec($time_hi_and_version);
    $time_hi_and_version = $time_hi_and_version >> 4;
    $time_hi_and_version = $time_hi_and_version | 0x4000;

    /**
     * Set the two most significant bits (bits 6 and 7) of the
     * clock_seq_hi_and_reserved to zero and one, respectively.
     */
    $clock_seq_hi_and_reserved = hexdec($clock_seq_hi_and_reserved);
    $clock_seq_hi_and_reserved = $clock_seq_hi_and_reserved >> 2;
    $clock_seq_hi_and_reserved = $clock_seq_hi_and_reserved | 0x8000;

    return sprintf('%08s-%04s-%04x-%04x-%012s', $time_low, $time_mid, $time_hi_and_version, $clock_seq_hi_and_reserved, $node);
} // guid

b
bish

If you use CakePHP you can use their method CakeText::uuid(); from the CakeText class to generate a RFC4122 uuid.


i
indextwo

Having searched for the exact same thing and almost implementing a version of this myself, I thought it was worth mentioning that, if you're doing this within a WordPress framework, WP has its own super-handy function for exactly this:

$myUUID = wp_generate_uuid4();

You can read the description and the source here.


The WP Function uses mt_rand exclusively. So might not have enough randomness
@HerbertPeters You're right. I only mentioned it because it's a one-liner. I was going to say that it would be neat if they had added a filter for it so you could return a more secure/guaranteed-random number; but the flipside of that is that, if you were so inclined, you could also return false 🤷
S
Serhii Polishchuk

Use Symfony Polyfill / Uuid
Then you can just generate uuid as native php function:

$uuid = uuid_create(UUID_TYPE_RANDOM);

More about it, read in official Symfony blop post - https://symfony.com/blog/introducing-the-new-symfony-uuid-polyfill


uuid_create(UUID_TYPE_TIME) to include the date. Note: this give a true UUID, not a fake one
C
Community

Inspired by broofa's answer here.

preg_replace_callback('/[xy]/', function ($matches)
{
  return dechex('x' == $matches[0] ? mt_rand(0, 15) : (mt_rand(0, 15) & 0x3 | 0x8));
}
, 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx');

Or if unable to use anonymous functions.

preg_replace_callback('/[xy]/', create_function(
  '$matches',
  'return dechex("x" == $matches[0] ? mt_rand(0, 15) : (mt_rand(0, 15) & 0x3 | 0x8));'
)
, 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx');

If you look at the comments in other answers, you would see people saying mt_rand() is not guaranteed randomness.
H
Hoan Dang

How about using mysql to generate the uuid for you?

$conn = new mysqli($servername, $username, $password, $dbname, $port);

$query = 'SELECT UUID()';
echo $conn->query($query)->fetch_row()[0];

MySQL's UUID() function creates v1 uuids.
a
amgine

From tom, on http://www.php.net/manual/en/function.uniqid.php

$r = unpack('v*', fread(fopen('/dev/random', 'r'),16));
$uuid = sprintf('%04x%04x-%04x-%04x-%04x-%04x%04x%04x',
    $r[1], $r[2], $r[3], $r[4] & 0x0fff | 0x4000,
    $r[5] & 0x3fff | 0x8000, $r[6], $r[7], $r[8])

What if they aren't running Unix or Linux/GNU? This code won't work.
This also has the potential of running very slowly if /dev/random is empty and is waiting for more entropy to reload.
/dev/urandom should be fine - /dev/random should only be used for generation of long term cryptographic keys.
Based on that, I came up with this - it uses several possible sources of randomness as fall-backs, and resorts to seeding mt_rand() if nothing fancier is available.
By now, just use random_bytes() in PHP 7 and off you go :-)
B
Baracus

I'm sure there's a more elegant way to do the conversion from binary to decimal for the 4xxx and yxxx portions. But if you want to use openssl_random_pseudo_bytes as your crytographically secure number generator, this is what I use:

return sprintf('%s-%s-%04x-%04x-%s',
    bin2hex(openssl_random_pseudo_bytes(4)),
    bin2hex(openssl_random_pseudo_bytes(2)),
    hexdec(bin2hex(openssl_random_pseudo_bytes(2))) & 0x0fff | 0x4000,
    hexdec(bin2hex(openssl_random_pseudo_bytes(2))) & 0x3fff | 0x8000,
    bin2hex(openssl_random_pseudo_bytes(6))
    );

s
simpleton

This could be simpler?

$uuid = bin2hex(openssl_random_pseudo_bytes(16));
for($cnt = 8; $cnt <=23; $cnt+=5)
   $uuid = substr($uuid, 0, $cnt) . "-" . substr($uuid, $cnt);

echo $uuid . "\n";

As it’s currently written, your answer is unclear. Please edit to add additional details that will help others understand how this addresses the question asked. You can find more information on how to write good answers in the help center.
n
notebook

Cryptographically secure UUID v4 for PHP >= 7.

<?php

function uuid4() {
    /* 32 random HEX + space for 4 hyphens */
    $out = bin2hex(random_bytes(18));

    $out[8]  = "-";
    $out[13] = "-";
    $out[18] = "-";
    $out[23] = "-";

    /* UUID v4 */
    $out[14] = "4";
    
    /* variant 1 - 10xx */
    $out[19] = ["8", "9", "a", "b"][random_int(0, 3)];

    return $out;
}

echo uuid4();

output: c68469d2-065b-4b17-b36f-5c40efb5f6cd


D
DaveInMaine

Just an idea, but what I ended up doing to get a V4 GUID was to use the database server. I am using SQL Server, and in the scenario where I needed the GUID, I was already running a query, so I just added newid() as one of the query result fields. That gave me the V4 GUID I needed.

This obviously depends on the database server you are using, and what else is going on in the code where you need the GUID (and how many GUIDs you need), but if your DB server generates v4 GUID, and especially if you are running a query anyway, that is a fast and simple PHP-version-independent way to get your GUID.