生成 v4 UUID 的 PHP 函数

Posted

技术标签:

【中文标题】生成 v4 UUID 的 PHP 函数【英文标题】:PHP function to generate v4 UUID 【发布时间】:2011-01-03 15:47:12 【问题描述】:

所以我一直在做一些挖掘工作,我一直在尝试拼凑一个在 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;


?>

【问题讨论】:

如果您在 Linux 上并且有点懒惰,您可以使用 $newId = exec('uuidgen -r'); 生成它们 你可以考虑使用这个库:github.com/abmmhasan/UUID 然后简单地使用命令:\AbmmHasan\Uuid::v4(); 【参考方案1】:

与其将其分解为单个字段,不如生成随机数据块并更改单个字节位置更容易。您还应该使用比 mt_rand() 更好的随机数生成器。

根据RFC 4122 - Section 4.4,您需要更改这些字段:

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

所有其他 122 位都应该足够随机。

以下方法使用openssl_random_pseudo_bytes()生成128位随机数据​​,对八位字节进行排列,然后使用bin2hex()vsprintf()进行最终格式化。

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

在 PHP 7 中,使用random_bytes() 生成随机字节序列更加简单:

function guidv4($data = null)

    $data = $data ?? random_bytes(16);
    // ...

【讨论】:

没有 openssl 扩展的 *nix 用户的替代方案:$data = file_get_contents('/dev/urandom', NULL, NULL, 0, 16); 另外,比起 mt_rand,我更信任 OpenSSL。 @BrunoAugusto 它是随机的,并且极不可能(具有良好的随机源)得到重复,但在数据库级别强制执行它是一个好习惯。 是否有任何理由不将 random_bytes(16) 调用放在 guidv4 函数中,从而不必将任何参数传递给 guidv4? 小改进:给$data设置一个NULL默认值,然后函数的第一行是这样的:$data = $data ?? random_bytes( 16 );现在你可以指定自己的随机数据源,或者让函数为你。 :-)【参考方案2】:

取自this对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 )
    );

【讨论】:

这个函数创建重复,所以当你需要唯一值时避免它。请注意,在给定相同种子的情况下,mt_rand() 将始终产生相同的随机数序列。因此,每次重复种子时,都会生成完全相同的 UUID。为了解决这个问题,您需要使用时间和 MAC 地址来播种,但我不确定您将如何做到这一点,因为 mt_srand() 需要一个整数。 @PavlePredic mt_srand(crc32(serialize([microtime(true), 'USER_IP', 'ETC']))); (我是另一个威廉:P) PHP 文档明确警告说 mt_rand() 不会生成加密安全值。换言之,此函数生成的值可能是可预测的。如果您需要确保 UUID 不可预测,您应该使用 Jack 下面的解决方案,该解决方案利用了 openssl_random_pseudo_bytes() 函数。 如果你用垃圾填满每个字段,生成 UUID 到底有什么意义? PHP 7.0+ 定义了函数 random_bytes() ,该函数将始终生成加密安全的随机字节,如果无法生成则抛出异常。这甚至比 openssl_random_psuedo_bytes() 更好,后者的输出有时在某些情况下不是加密安全的。【参考方案3】:

任何使用 composer 依赖项的人,您可能需要考虑这个库:https://github.com/ramsey/uuid

没有比这更容易的了:

Uuid::uuid4();

【讨论】:

哦,我不知道.... 五行代码与加载具有依赖关系的库?我更喜欢杰克的功能。 YMMV +1 给斯蒂芬。 Ramsey uuid 的功能远不止 uuid4。我不要香蕉!,这里有整个丛林! UUID 不仅仅是随机字符串。它的工作原理有一个规范。为了生成一个我不必担心以后会被拒绝的正确随机 UUID,我宁愿使用经过测试的库,也不愿推出自己的实现。 这是一个 UUIDv4。它(主要是,但对于少数)是随机的。这不是密码学。对“自己动手”的偏执是愚蠢的。 不存在使用库的开销,它有测试。 +1 不重新发明***。【参考方案4】:

在 unix 系统上,使用系统内核为您生成一个 uuid。

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

https://serverfault.com/a/529319/210994 上的 Samveen 信用

注意!:使用这种方法获取 uuid 实际上会很快耗尽熵池!我会避免在经常调用它的地方使用它。

【讨论】:

除了可移植性之外,请注意随机源是/dev/random,如果熵池耗尽,则会阻塞。 @Jack 能否请您链接一些有关 unix 系统上熵池耗尽主题的文档?我很想了解更多关于这种方法失效的实际用例。 我无法从/dev/urandom 找到有关制作此特殊内核文件源的信息,据我了解,这不会用尽,但有返回重复 uuid 的风险。我想这是一个权衡;你真的需要一个受系统熵影响的唯一 id 吗? 我曾经注意到,通过 linux 内核(共享资源)获取 uuid 足以保证同一系统上的唯一 uuid。我相信这个 procfs uuid 以这种方式使用是安全的。请注意,有多个版本的 UUID en.wikipedia.org/wiki/… 和 linux 通常可能会给你版本 3 和 5 类型 man7.org/linux/man-pages/man3/uuid_generate.3.html 这些解决方案对我来说真的很有趣。好笑!=不好【参考方案5】:

Jack's answer 的细微变化以添加对 PHP

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

【讨论】:

【参考方案6】:
// php version >= 7
$uuid = vsprintf('%s%s-%s-%s-%s-%s%s%s', str_split(bin2hex(random_bytes(16)), 4));

【讨论】:

请在您的代码中添加解释,以帮助其他人理解它的作用。 这是 Symfony polyfil 实际所做的 - github.com/symfony/polyfill-uuid/blob/master/Uuid.php#L320 这是正确的吗?快速测试返回c0a062b7-b225-c294-b8a0-06b98931a45b,与 xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx 不匹配。它返回一个 c 而不是 4。【参考方案7】:

在搜索创建 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));

信用:pavel.volyntsev

编辑:澄清一下,这个函数总是会给你一个 v4 uuid (PHP >= 5.3.0)。

当 com_create_guid 函数可用时(通常仅在 Windows 上),它将使用该函数并去掉花括号。

如果不存在(Linux),它将依赖这个强大的随机 openssl_random_pseudo_bytes 函数,然后使用 vsprintf 将其格式化为 v4 uuid。

【讨论】:

【参考方案8】:

我的回答基于评论uniqid user comment,但它使用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

【讨论】:

【参考方案9】:

如果您使用CakePHP,您可以使用CakeText 类中的CakeText::uuid(); 方法来生成RFC4122 uuid。

【讨论】:

【参考方案10】:

灵感来自broofa 的回答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');

或者如果无法使用匿名函数。

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

【讨论】:

如果您查看其他答案中的 cmets,您会看到有人说 mt_rand() 不能保证随机性。【参考方案11】:

搜索了完全相同的东西并几乎自己实现了一个版本,我认为值得一提的是,如果您在 WordPress 中执行此操作框架,WP 有自己的超级方便的功能:

$myUUID = wp_generate_uuid4();

您可以阅读描述和来源here。

【讨论】:

WP 函数只使用 mt_rand。所以可能没有足够的随机性 @HerbertPeters 你是对的。我只提到它是因为它是单行的。我要说的是,如果他们为它添加了一个过滤器,那就太好了,这样你就可以返回一个更安全/有保证的随机数;但另一方面,如果你愿意,你也可以返回false?【参考方案12】:

使用Symfony Polyfill / Uuid 然后你可以生成 uuid 作为原生 php 函数:

$uuid = uuid_create(UUID_TYPE_RANDOM);

关于它的更多信息,请阅读官方 Symfony blop 帖子 - https://symfony.com/blog/introducing-the-new-symfony-uuid-polyfill

【讨论】:

uuid_create(UUID_TYPE_TIME) 包括日期。注意:这给出了一个真正的 UUID,而不是一个假的【参考方案13】:

mysql为你生成uuid怎么样?

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

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

【讨论】:

MySQL 的 UUID() 函数创建 v1 uuid。【参考方案14】:

来自汤姆,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])

【讨论】:

如果他们不运行 Unix 或 Linux/GNU 怎么办?此代码不起作用。 如果 /dev/random 为空并且等待更多熵重新加载,这也有可能运行非常缓慢。 /dev/urandom 应该没问题 - /dev/random 应该只用于生成长期加密密钥。 在此基础上,我提出了this - 它使用了几种可能的随机来源作为后备,如果没有更好的可用,则使用播种mt_rand() 现在,只需在 PHP 7 中使用 random_bytes() 即可:-)【参考方案15】:

我确信有一种更优雅的方法可以将4xxxyxxx 部分从二进制转换为十进制。但是,如果您想使用 openssl_random_pseudo_bytes 作为您的密码安全数字生成器,这就是我使用的:

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

【讨论】:

【参考方案16】:

这可能更简单吗?

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

【讨论】:

正如目前所写,您的答案尚不清楚。请edit 添加其他详细信息,以帮助其他人了解这如何解决所提出的问题。你可以找到更多关于如何写好答案的信息in the help center。【参考方案17】:

只是一个想法,但我最终获得 V4 GUID 的方法是使用数据库服务器。我正在使用 SQL Server,在需要 GUID 的场景中,我已经在运行查询,所以我只是将 newid() 添加为查询结果字段之一。这给了我所需的 V4 GUID。

这显然取决于您使用的数据库服务器,以及您需要 GUID 的代码中的其他内容(以及您需要多少 GUID),但是如果您的数据库服务器生成 v4 GUID,尤其是如果您无论如何都在运行查询,这是一种快速且简单的独立于 PHP 版本的方式来获取您的 GUID。

【讨论】:

以上是关于生成 v4 UUID 的 PHP 函数的主要内容,如果未能解决你的问题,请参考以下文章

使用 UUID v4 生成会话 ID 是不是安全?

vue中使用唯一标识uuid——uuid.v1()-时间戳uuid.v4()-随机数

插入php后获取生成的uuid

插入php后获取生成的uuid

PHP生成 uuid

php 纯PHP UUID生成器