生成v4 UUID的PHP函数

311

所以我一直在探索如何在PHP中生成有效的v4 UUID函数。这是我能做到的最接近的方法。但是,我对于十六进制、十进制、二进制、PHP位运算符等方面的知识几乎没有。这个函数可以生成一个有效的v4 UUID,但有一个问题。v4 UUID应该是这种形式:

xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx

其中y应该为8、9、A或B。但这个函数没有遵守这个规则。

我希望有更多相关知识的人可以帮我解决这个问题。

以下是该函数:

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

?>

11
如果您使用Linux,并且有点懒,您可以通过$newId = exec('uuidgen -r');来生成它们。 - JorgeGarza
你可以考虑使用这个库:https://github.com/abmmhasan/UUID 然后只需使用以下命令:\AbmmHasan\Uuid::v4(); - abmmhasan
19个回答

450

格式化

要构建UUIDv4,您可以生成128位的随机数据,修补一些字段使其符合标准,然后将其格式化为十六进制组。

根据RFC 4122 - Section 4.4,您需要更改以下位:

  1. time_hi_and_version(第7个八位字节的4-7位),
  2. clock_seq_hi_and_reserved(第9个八位字节的6和7位)

下面的代码对给定数据进行排列组合,然后使用bin2hex()vsprintf()进行最终格式化。

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

生成随机数据

为了生成加密强度的随机数据,建议使用 openssl_random_pseudo_bytes()random_bytes() (php7 及以上版本),而不是使用 mt_rand()

以下是使用后者实现的示例:

echo format_uuidv4(random_bytes(16));

对于openssl_random_pseudo_bytes()函数,务必确保结果具有加密强度:

$data = openssl_random_pseudo_bytes(16, $strong);
assert($data !== false && $strong);
echo format_uuidv4($data);

9
如果*nix用户没有openssl扩展,可以使用另一种方法:$data = file_get_contents('/dev/urandom', NULL, NULL, 0, 16); - Iiridayn
7
我比较相信OpenSSL,而不是mt_rand。 - Prof. Falken
5
@BrunoAugusto 这是随机的,而且(如果有一个好的随机源)重复的可能性非常小,但在数据库级别执行此操作是一个很好的实践。 - Ja͢ck
12
把 random_bytes(16) 函数放在 guidv4 函数内部,这样 guidv4 函数就不需要传递任何参数了。有没有不这么做的理由? - Stephen R
7
小改进:为 $data 设置一个 NULL 默认值,然后函数的第一行是这样的:$data = $data ?? random_bytes(16); 现在你可以指定自己的随机数据源,或者让函数自动为您生成。 :-) - Stephen R
显示剩余3条评论

330

摘自PHP手册上的评论,你可以使用以下代码:

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

是啊,哇。我本来以为我已经检查过uniqid的注释了。但是我有几个问题想问一下,关于那个函数和我的函数有什么区别?生成块的方式有什么不同吗?例如,节点是通过3个16位块还是6个8位块来生成的?最后,与位移和从mt_rand()中获取有什么区别吗?再次感谢。 - anomareh
60
当您需要唯一值时,请避免使用此函数,因为它会创建重复项。请注意,给定相同的种子,mt_rand()总是会产生相同的随机数序列。因此,每次重复种子时都会生成完全相同的UUID。要解决这个问题,您需要使用时间和Mac地址对其进行种子处理,但我不确定该如何操作,因为mt_srand()需要一个整数作为参数。 - Pavle Predic
15
@PavlePredic mt_srand(crc32(serialize([microtime(true), 'USER_IP', 'ETC'])));(我是另一个威廉 :P) - Wiliam
14
PHP文档明确警告说mt_rand()函数不能生成加密安全的值。换句话说,由该函数生成的值可能是可预测的。如果您需要确保UUID不可预测,应该使用下面Jack提供的解决方案,该方案使用openssl_random_pseudo_bytes()函数。 - Richard Keller
11
如果您在每个字段都填充垃圾数据,生成UUID还有什么意义? - Eevee
3
PHP 7.0+ 定义了函数 random_bytes(),它将始终生成加密安全的随机字节或在无法生成时抛出异常。这比甚至 openssl_random_psuedo_bytes() 更好,后者在某些情况下输出有时不是加密安全的。 - thomasrutter

154

54
哦,我不知道... 五行代码还是加载具有依赖项的库?我更喜欢杰克的函数。结果可能因人而异。 - Stephen R
9
+1 给 Stephen。Ramsey 的 uuid 拥有比仅 uuid4 更多的功能。我想要一个香蕉!这里你有整个丛林! - lcjury
70
UUID(通用唯一识别码)并不只是随机字符串。它有一个规范规定了它的工作方式。为了生成一个合适的随机UUID,我宁愿使用经过测试的库而不是自己实现。这可以避免后续出现被拒绝的情况。 - Brandon
6
这是一个 UUIDv4。它(大部分时间,但其中有一些位不是)是随机生成的。这并不涉及加密。对“自己编写”的过度谨慎是无意义的。 - Gordon
13
使用该库不会增加任何开销,并且它还有测试。因为没有重复造轮子,所以这个库得到了加分。 - istepaniuk
显示剩余4条评论

39

在Unix系统上,可以使用系统内核为您生成UUID。

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

https://serverfault.com/a/529319/210994上给予Samveen信用。

注意:使用此方法获取uuid确实会非常快地消耗熵池!在需要频繁调用的情况下,请避免使用此方法。


2
除了可移植性之外,请注意随机源是/dev/random,如果熵池耗尽,则会阻塞。 - Ja͢ck
@Jack,你能否提供一些关于Unix系统熵池耗尽的文档链接吗?我很想了解更多关于这种方法失效的实际使用情况。 - ThorSummoner
我找不到有关如何从/dev/urandom制作此特殊内核文件源的信息,据我所知,这不会用尽,但会存在返回重复UUID的风险。我想这是一个权衡;您是否真正需要受系统熵影响的唯一ID? - ThorSummoner
我曾经注意到,通过Linux内核(一个共享资源)获取uuid足以保证在同一系统上具有唯一的uuid。我相信这个procfs uuid是安全使用的。请注意,UUID有多个版本https://en.wikipedia.org/wiki/Universally_unique_identifier#Versions,而procfs和Linux通常会给您提供第3版和第5版类型https://man7.org/linux/man-pages/man3/uuid_generate.3.html。 - ThorSummoner
这类解决方案对我来说真的很有趣。有趣并不等于糟糕。 - jack
Systemd还提供了一个“获取UUID”的接口,经过测试,我发现它们最终都使用相同的内核UUID生成器(因为我们使用Linux作为共享资源来确保UUID锁定),所以无论您使用什么接口(proc fs、syscall、systemd bus),它都是相同的锁和相同的生成代码,所以我不知道,获取文件不会对我造成任何伤害。 - ThorSummoner

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

6
请为您的代码添加说明,以帮助其他人理解它的作用。 - user5446912
这是Symfony polyfill实际完成的内容 - https://github.com/symfony/polyfill-uuid/blob/master/Uuid.php#L320 - Serhii Polishchuk
2
这正确吗?快速测试返回了c0a062b7-b225-c294-b8a0-06b98931a45b,它与xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx不匹配。它返回了一个c而不是4。 - Rafael

25

Jack的答案进行了轻微修改,以支持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));
}

18

在寻找创建v4 uuid的过程中,我先来到了这个页面,然后在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

编辑说明: 这个函数将始终给您一个v4 uuid (PHP >= 5.3.0)。

如果可用, 将使用com_create_guid函数(通常仅在Windows上), 并剥离花括号。

如果不可用(Linux), 则会借助于这个强大的随机openssl_random_pseudo_bytes函数,然后使用vsprintf将其格式化为v4 uuid。


7
如果您使用 CakePHP,您可以使用他们的方法 CakeText::uuid(); 来自 CakeText 类来生成一个 RFC4122 uuid。

6

我的回答基于评论uniqid用户评论,但是它使用openssl_random_pseudo_bytes函数生成随机字符串,而不是从/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

6

2
uuid_create(UUID_TYPE_TIME)可以包含日期。注意:这将生成真正的UUID,而不是伪造的。 - atmacola

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接