生成 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】:
我确信有一种更优雅的方法可以将4xxx
和yxxx
部分从二进制转换为十进制。但是,如果您想使用 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 函数的主要内容,如果未能解决你的问题,请参考以下文章