306

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

?>
danronmoon
  • 3,814
  • 5
  • 34
  • 56
anomareh
  • 5,294
  • 4
  • 25
  • 22
  • 11
    If you are on Linux and if you are a little lazy you can generete them with `$newId = exec('uuidgen -r');` – JorgeGarza Jan 05 '17 at 19:46
  • You may consider using this library: https://github.com/abmmhasan/UUID then simply use the command: \AbmmHasan\Uuid::v4(); – abmmhasan Nov 19 '21 at 16:08

19 Answers19

443

Formatting

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 format_uuidv4($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));
}

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

An example of how this is implemented with the latter:

echo format_uuidv4(random_bytes(16));

For openssl_random_pseudo_bytes(), take care to ensure the result is cryptographically strong:

$data = openssl_random_pseudo_bytes(16, $strong);
assert($data !== false && $strong);
echo format_uuidv4($data);
Ja͢ck
  • 170,779
  • 38
  • 263
  • 309
  • 9
    An alternative for *nix users who don't have the openssl extension: `$data = file_get_contents('/dev/urandom', NULL, NULL, 0, 16);` – Iiridayn Apr 19 '13 at 20:49
  • 6
    Also, I would trust OpenSSL a lot more than mt_rand. – Prof. Falken May 24 '13 at 07:08
  • 1
    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? –  Jul 21 '16 at 13:07
  • 5
    @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. – Ja͢ck Jul 21 '16 at 14:04
  • 12
    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? – Stephen R Mar 08 '17 at 23:59
  • 1
    @StephenR it allows for the caller to choose how they want to generate the random data. – Ja͢ck Feb 02 '18 at 08:27
  • 7
    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. :-) – Stephen R May 17 '18 at 17:23
  • @Iiridayn or `uuidgen` from util-linux 2.34 – undefined May 20 '21 at 13:20
328

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 )
    );
}
William
  • 15,465
  • 8
  • 36
  • 32
  • 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. – anomareh Jan 11 '10 at 07:43
  • 59
    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. – Pavle Predic Mar 07 '13 at 09:27
  • 15
    @PavlePredic mt_srand(crc32(serialize([microtime(true), 'USER_IP', 'ETC']))); (i'm another wiliam :P) – Wiliam Mar 24 '13 at 13:41
  • 14
    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. – Richard Keller Jul 17 '13 at 08:36
  • 11
    what on earth is the point of generating a UUID if you fill every field with garbage? – Eevee May 27 '16 at 06:40
  • 3
    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. – thomasrutter Jul 12 '19 at 01:51
152

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();
djule5
  • 2,722
  • 2
  • 19
  • 19
  • 54
    Oh, I don't know.... Five lines of code vs. loading a library with dependencies? I prefer Jack's function. YMMV – Stephen R May 17 '18 at 23:25
  • 9
    +1 to Stephen. Ramsey uuid has a lot more functionality than just uuid4. I wan't a banana!, here you have the entire jungle! – lcjury Jul 04 '18 at 17:43
  • 69
    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. – Brandon Nov 19 '18 at 15:27
  • 6
    It's a UUIDv4. It's (mostly, but for a few bits) random. This ain't cryptography. Paranoia against "rolling your own" is silly. – Gordon Feb 06 '19 at 21:08
  • 13
    The overhead of using the library is non-existent, and it has *tests*. +1 for not reinventing the wheel. – istepaniuk Oct 06 '20 at 11:56
  • 1
    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. – Ryan Rentfro Sep 19 '21 at 04:01
  • Laravel 6 + already has this package – ÄR Âmmãř Żąîñh Feb 07 '23 at 06:16
  • "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. – Andrew Koster Apr 10 '23 at 23:38
  • 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) – Stephen R Apr 12 '23 at 22:56
39

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.

ThorSummoner
  • 16,657
  • 15
  • 135
  • 147
  • 2
    Besides portability, note that the random source is `/dev/random` which blocks if the entropy pool is exhausted. – Ja͢ck Apr 19 '14 at 20:39
  • @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. – ThorSummoner Apr 19 '14 at 21:40
  • 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? – ThorSummoner Jun 05 '15 at 17:35
  • 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 https://en.wikipedia.org/wiki/Universally_unique_identifier#Versionsprocfs and linux in general probably give you Version 3 and 5 types https://man7.org/linux/man-pages/man3/uuid_generate.3.html – ThorSummoner Dec 17 '20 at 20:30
  • These kind of solutions are really funny to me. Funny != bad – jack May 17 '21 at 19:48
  • 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 any – ThorSummoner May 18 '21 at 23:56
27
// php version >= 7
$uuid = vsprintf('%s%s-%s-%s-%s-%s%s%s', str_split(bin2hex(random_bytes(16)), 4));
  • 6
    Please add an explanation to your code to help others understand what it does. –  Jan 16 '20 at 19:53
  • this is what actually done by Symfony polyfil - https://github.com/symfony/polyfill-uuid/blob/master/Uuid.php#L320 – Serhii Polishchuk May 14 '20 at 10:04
  • 2
    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. – Rafael Oct 06 '21 at 17:00
23

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));
}
Danny Beckett
  • 20,529
  • 24
  • 107
  • 134
17

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.

Arie
  • 640
  • 8
  • 16
6

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

bish
  • 3,381
  • 9
  • 48
  • 69
5

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
Victor Smirnov
  • 3,450
  • 4
  • 30
  • 49
5

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

Serhii Polishchuk
  • 1,442
  • 17
  • 22
4

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.

indextwo
  • 5,535
  • 5
  • 48
  • 63
  • 2
    The WP Function uses mt_rand exclusively. So might not have enough randomness – Herbert Peters Mar 20 '19 at 03:00
  • @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` – indextwo Mar 20 '19 at 11:02
4

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

notebook
  • 63
  • 5
3

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');
Community
  • 1
  • 1
Michael
  • 11,912
  • 6
  • 49
  • 64
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];
Hoan Dang
  • 366
  • 1
  • 6
  • 14
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))
    );
Baracus
  • 580
  • 7
  • 15
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])
amgine
  • 19
  • 1
  • 3
    What if they aren't running Unix or Linux/GNU? This code won't work. – Cole Tobin Aug 23 '12 at 00:56
  • 4
    This also has the potential of running very slowly if /dev/random is empty and is waiting for more entropy to reload. – ObsidianX Sep 07 '12 at 23:52
  • 1
    `/dev/urandom` should be fine - `/dev/random` should only be used for generation of long term cryptographic keys. – Iiridayn Apr 19 '13 at 20:24
  • Based on that, I came up with [this](https://gist.github.com/mindplay-dk/fb3589d711df80c43264) - it uses several possible sources of randomness as fall-backs, and resorts to seeding `mt_rand()` if nothing fancier is available. – mindplay.dk Jun 05 '15 at 16:28
  • 1
    By now, just use `random_bytes()` in PHP 7 and off you go :-) – mindplay.dk Jun 28 '17 at 08:22
1

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";
simpleton
  • 11
  • 1
  • 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](/help/how-to-ask). – Community Sep 11 '21 at 03:06
0

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

      $uuid = uuid_create();
      echo $uuid;
-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.

DaveInMaine
  • 899
  • 9
  • 12