PHP函数生成v4 UUID


233

因此,我一直在做一些挖掘工作,并试图拼凑一个在PHP中生成有效v4 UUID的函数。这是我能找到的最接近的。我对十六进制,十进制,二进制,PHP的按位运算符之类的知识几乎不存在。该功能会生成一个有效的v4 UUID,直到一个区域。v4 UUID的形式应为:

xxxxxxxx-xxxx- 4 xxx- y xxx-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;
}

?>

感谢任何可以帮助我的人。


5
如果您使用的是Linux,并且比较懒惰,可以使用$newId = exec('uuidgen -r');
JorgeGarza

Answers:


282

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

43
此函数创建重复项,因此在需要唯一值时避免重复。注意,给定相同的种子,mt_rand()将始终产生相同的随机数序列。因此,每次重复种子,都会生成相同的精确UUID。为了解决这个问题,您需要使用时间和mac地址作为种子,但是我不确定如何做到这一点,因为mt_srand()需要一个整数。
Pavle Predic

12
@PavlePredic mt_srand(crc32(serialize([microtime(true),'USER_IP','ETC'])))));; (我是另一个威廉·:P)
威廉·

13
PHP文档明确警告mt_rand()不会生成加密安全值。换句话说,此函数生成的值可能是可预测的。如果需要确保UUID不可预测,则应使用下面的Jack解决方案,该解决方案利用openssl_random_pseudo_bytes()函数。
理查德·凯勒2013年

7
如果用垃圾填满每个字段,生成UUID到底有什么意义?
伊夫

1
PHP 7.0+定义了函数random_bytes(),该函数将始终生成加密安全的随机字节,如果无法生成则抛出异常。这甚至比openssl_random_psuedo_bytes()更好,后者在某些情况下有时输出的密码也不安全。
thomasrutter

365

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

根据RFC 4122-第4.4节,您需要更改以下字段:

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

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

以下方法使用生成128位随机数据openssl_random_pseudo_bytes(),在八位位组上进行排列,然后使用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);
    // ...
}

9
不具有openssl扩展名的* nix用户的一种替代选择:$data = file_get_contents('/dev/urandom', NULL, NULL, 0, 16);
Iiridayn

5
另外,我比mt_rand更信任OpenSSL。
法肯教授

3
@BrunoAugusto是随机的,并且极不可能(具有良好的随机源)获取重复项,但是在数据库级别实施它是一个好习惯。
杰克·

9
是否有任何理由不将random_bytes(16)调用放入guidv4函数中,从而不必将任何参数传递给guidv4?
斯蒂芬·R

7
小改进:为$ data设置一个NULL默认值,然后函数的第一行是这样的:$data = $data ?? random_bytes( 16 ); 现在,您可以指定自己的随机数据源,或者让函数为您完成。:-)
Stephen R

118

任何使用作曲家依赖关系的人,您可能都想考虑以下库:https : //github.com/ramsey/uuid

没有比这更容易的了:

Uuid::uuid4();

32
哦,我不知道....五行代码与加载具有依赖项的库?我更喜欢杰克的功能。YMMV
Stephen R

7
+1斯蒂芬。Ramsey uuid不仅具有uuid4,还具有更多的功能。我要香蕉!这里有整个丛林!
lcjury

26
UUID不仅仅是随机字符串。有一个如何工作的规范。为了生成适当的随机UUID,而我不必担心以后会被拒绝,我宁愿使用经过测试的库,也不愿滚动自己的实现。
布兰登

3
这是一个UUIDv4。它是随机的(大多数情况下,但有一点点)。这不是密码学。反对“自己动手”的偏执是愚蠢的。
Gordon

23

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

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

信贷Samveen在https://serverfault.com/a/529319/210994

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


2
除了可移植性之外,请注意,/dev/random如果熵池耗尽,则随机源是哪个阻塞。
2014年

@Jack能否请您链接一些有关unix系统上熵池耗尽的文档?我想对这种方法失效的实际用例有更多的了解。
ThorSummoner 2014年

我无法从中找到有关制作此特殊内核文件源的信息/dev/urandom,据我理解,该信息不会穷尽,但有返回返回的uuid的风险。我想这是一个权衡;您是否真的需要受系统熵影响的唯一ID?
ThorSummoner 2015年

13

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


5

我的答案基于评论唯一用户评论,但它使用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

5

如果你使用CakePHP,你可以用自己的方法CakeText::uuid();CakeText类来生成一个RFC4122 UUID。


5

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

4

灵感来自broofa的答案在这里

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

1
如果您查看其他答案中的评论,您会看到人们说mt_rand()不能保证随机性。
张学友

3

有搜索的确切同样的事情,几乎实现一个版本的这一点我自己,我认为这是值得一提的是,如果你是一个做内这个WordPress的框架,WP具有正是这种自身的超方便的功能:

$myUUID = wp_generate_uuid4();

您可以在此处阅读说明和源。


1
WP函数仅使用mt_rand。因此可能没有足够的随机性
赫伯特·彼得斯

@HerbertPeters你是对的。我只提到它是因为它是单线的。我要说的是,如果他们添加了一个过滤器,那就太好了,这样您就可以返回一个更安全/有保证的随机数;但是相反的是,如果您如此倾斜,还可以返回false🤷–
indextwo

2

如何使用mysql为您生成uuid?

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

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

2
MySQL的UUID()函数创建v1 uuid。
staticsan '17年


1

从汤姆,在http://www.php.net/manual/zh/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])

3
如果他们没有运行Unix或Linux / GNU,该怎么办?此代码无效。
科尔·约翰逊

4
如果/ dev / random为空,并且正在等待更多的熵重新加载,那么这也有可能会非常缓慢地运行。
ObsidianX 2012年

1
/dev/urandom应该很好- /dev/random应该仅用于生成长期加密密钥。
Iiridayn

基于此,我想到了这一点 -它使用了几种可能的随机性来源作为后备,mt_rand()如果没有更好的选择,则诉诸于播种。
mindplay.dk 2015年

1
到现在random_bytes()
为止

1

我敢肯定,对于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))
    );

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.