PHP function to generate v4 UUID
Asked Answered
N

19

321

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

?>
Nutbrown answered 11/1, 2010 at 6:17 Comment(2)
If you are on Linux and if you are a little lazy you can generete them with $newId = exec('uuidgen -r');Kolinsky
You may consider using this library: github.com/abmmhasan/UUID then simply use the command: \AbmmHasan\Uuid::v4();Linton
F
332

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 )
    );
}
Furious answered 11/1, 2010 at 6:27 Comment(6)
Yeah, wow. Coulda' sworn I checked through the comments on uniqid too. Just a few questions about that function compared to mine though. Is there any difference in how the chunks are generated? I.E. for the node generating it in 3 16 bit chunks vs 6 8 bit chunks? Lastly, any differences vs the bit shifting and just grabbing from mt_rand()? Thanks again.Nutbrown
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.Fosse
@PavlePredic mt_srand(crc32(serialize([microtime(true), 'USER_IP', 'ETC']))); (i'm another wiliam :P)Antifederalist
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.Ivelisseivens
what on earth is the point of generating a UUID if you fill every field with garbage?Airscrew
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.Ezekielezell
M
463

To construct a UUIDv4 you can generate 128 bits worth of random data, patch a few fields to make the data comply with the standard, and then format it as hexadecimal groups.

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

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

The below code makes the permutations on the given data and then uses bin2hex() and vsprintf() to do the final formatting.

function uuidv4()
{
  $data = 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));
}

PHP 5 and older

The random_bytes function was introduced in PHP 7.0. An example of how this can be implemented in older versions of PHP:

$data = openssl_random_pseudo_bytes(16, $strong);
// ensure the result is cryptographically strong
assert($data !== false && $strong);

Generating random data

To generate cryptographically strong random data, instead of using mt_rand(), it's recommended to use either openssl_random_pseudo_bytes() or random_bytes() (php7 onwards). You can also choose another method for generating the random data.

Margaritamargarite answered 8/4, 2013 at 9:21 Comment(9)
An alternative for *nix users who don't have the openssl extension: $data = file_get_contents('/dev/urandom', NULL, NULL, 0, 16);Garpike
Also, I would trust OpenSSL a lot more than mt_rand.Merralee
Many years later I stumbled upon your answer and, although I've searched myself, I have a question: is this random or in a practical scenario should I add an additional verification on database that looks after the sequence generated?Jarrettjarrid
@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.Carlson
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?Spherics
@StephenR it allows for the caller to choose how they want to generate the random data.Carlson
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. :-)Spherics
@Garpike or uuidgen from util-linux 2.34Waylay
@Margaritamargarite Generally you should leave as little room for error as possible. Passing $data as an argument leaves the responsibility of the security to the user of the function. You should do that only if it's absolutely necessary, not by default for convenience. Especially not in a StackOverflow answer.Chihuahua
F
332

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 )
    );
}
Furious answered 11/1, 2010 at 6:27 Comment(6)
Yeah, wow. Coulda' sworn I checked through the comments on uniqid too. Just a few questions about that function compared to mine though. Is there any difference in how the chunks are generated? I.E. for the node generating it in 3 16 bit chunks vs 6 8 bit chunks? Lastly, any differences vs the bit shifting and just grabbing from mt_rand()? Thanks again.Nutbrown
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.Fosse
@PavlePredic mt_srand(crc32(serialize([microtime(true), 'USER_IP', 'ETC']))); (i'm another wiliam :P)Antifederalist
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.Ivelisseivens
what on earth is the point of generating a UUID if you fill every field with garbage?Airscrew
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.Ezekielezell
Z
157

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();
Zinc answered 16/7, 2014 at 16:23 Comment(9)
Oh, I don't know.... Five lines of code vs. loading a library with dependencies? I prefer Jack's function. YMMVSpherics
+1 to Stephen. Ramsey uuid has a lot more functionality than just uuid4. I wan't a banana!, here you have the entire jungle!Horsefaced
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.Apartment
It's a UUIDv4. It's (mostly, but for a few bits) random. This ain't cryptography. Paranoia against "rolling your own" is silly.Laryngoscope
The overhead of using the library is non-existent, and it has tests. +1 for not reinventing the wheel.Glomeration
When you assemble strings of random bits they are just that. A UUID4 has 6 pre-determined bits of the total 128 that identify the type of the UUID and it's encoding. 5 lines of code is not compliant with the spec and dangerous to run in high concurrenct situations due to how you would assemble random strings with PHP.Phebe
Laravel 6 + already has this packagePunnet
"Jack's function" has an OPERATING SYSTEM DEPENDENCY (openssl_random_pseudo_bytes) which SOMETIMES USES CRYPTOGRAPHICALLY WEAK ALGORITHMS to produce the random bytes. Furthermore, when it does this, it will only tell you that it did this if you happen to check an OUTPUT PARAMETER and throw your own exception (which Jack's function does not do). I'll take the well-tested library that is managed by the package manager.Printing
And here we are in 2023, and hopefully all on PHP 7 or higher. No longer need openssl_ function when we have random_bytes() (as observed in Jack's post)Spherics
F
41

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.

Freeman answered 17/4, 2014 at 19:4 Comment(7)
Besides portability, note that the random source is /dev/random which blocks if the entropy pool is exhausted.Carlson
@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.Freeman
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?Freeman
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.htmlFreeman
These kind of solutions are really funny to me. Funny != badDrusie
Systemd also provides a "get a uuid" interface somewhere, in testing I found that they all ultimately use the same kernal uuid generator (because we're using linux as the shared resource to ensure uuid locks), so no matter what interface your using (proc fs, syscall, systemd bus) its the same lock and same generating code, so idk, getting the file doesn't hurt me anyFreeman
this is such a good and simple answer! Thanks :)Warms
S
31
// php version >= 7
$uuid = vsprintf('%s%s-%s-%s-%s-%s%s%s', str_split(bin2hex(random_bytes(16)), 4));
Skinned answered 16/1, 2020 at 18:49 Comment(3)
Please add an explanation to your code to help others understand what it does.Institution
this is what actually done by Symfony polyfil - github.com/symfony/polyfill-uuid/blob/master/Uuid.php#L320Marrow
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.Infante
K
26

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));
}
Karli answered 31/3, 2019 at 9:48 Comment(0)
T
19

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.

Twilley answered 12/6, 2017 at 17:3 Comment(0)
L
7

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

Loader answered 7/9, 2017 at 18:33 Comment(0)
M
7

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

Marrow answered 14/5, 2020 at 9:58 Comment(1)
uuid_create(UUID_TYPE_TIME) to include the date. Note: this give a true UUID, not a fake oneLeucoderma
F
7

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

Flathead answered 12/7, 2022 at 5:13 Comment(0)
M
6

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
Miterwort answered 11/1, 2010 at 6:17 Comment(0)
C
5

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.

Colunga answered 12/10, 2018 at 19:36 Comment(2)
The WP Function uses mt_rand exclusively. So might not have enough randomnessLegalism
@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 🤷Colunga
L
3

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];
Levo answered 26/5, 2017 at 1:47 Comment(1)
MySQL's UUID() function creates v1 uuids.Rovelli
H
2

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');
Highclass answered 22/3, 2013 at 12:34 Comment(1)
If you look at the comments in other answers, you would see people saying mt_rand() is not guaranteed randomness.Shrive
C
2

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))
    );
Conover answered 25/1, 2019 at 21:14 Comment(1)
i think this is the most elegant solution (function names are long and ugly, but they are what they are)Hosanna
R
2

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";
Rothermere answered 11/9, 2021 at 2:44 Comment(1)
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.Dobson
A
1

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])
Amphetamine answered 22/8, 2012 at 18:12 Comment(5)
What if they aren't running Unix or Linux/GNU? This code won't work.Numbing
This also has the potential of running very slowly if /dev/random is empty and is waiting for more entropy to reload.Gluttony
/dev/urandom should be fine - /dev/random should only be used for generation of long term cryptographic keys.Garpike
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.Greatcoat
By now, just use random_bytes() in PHP 7 and off you go :-)Greatcoat
P
0

The uuid_create function, which is available in PHP 7.2 and later, can be used.

      $uuid = uuid_create();
      echo $uuid;
Photodynamics answered 2/9, 2023 at 13:30 Comment(1)
On PHP 8.1, this doesn't work for me. Perhaps you're using other libraries.Kendry
G
-1

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.

Gerent answered 20/9, 2021 at 16:29 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.