开发者

PHP function to generate v4 UUID

开发者 https://www.devze.com 2022-12-16 04:00 出处:网络
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, deci

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(开发者_C百科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;
}

?>


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:

  1. time_hi_and_version (bits 4-7 of 7th octet),
  2. 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);
    // ...
}


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


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();


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.


// php version >= 7
$uuid = vsprintf('%s%s-%s-%s-%s-%s%s%s', str_split(bin2hex(random_bytes(16)), 4));


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


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.


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


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


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


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.


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');


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


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


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


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


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


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.

0

精彩评论

暂无评论...
验证码 换一张
取 消