通用唯一识别码(英语:Universally Unique
Identifier,缩写:UUID)是用于计算机体系中以识别信息的一个128位标识符。
UUID按照标准方法生成时,在实际应用中具有唯一性,且不依赖中央机构的注册和分配。UUID重复的概率接近零,可以忽略不计[1][2]。
因此,所有人都可以自行建立和使用UUID,而且几乎可以确定其不会与既有的标识符重复。也因为如此,在不同地方产生的UUID可以使用于同一个数据库或同一个频道中,而且几乎不可能重复。
UUID的应用相当普遍,许多计算平台都提供了对于生成和解析UUID的支持。
1990年代, UUID
原本是用于阿波罗电脑的网络计算系统,后被用于开放软件基金会的分布式运算环境。分布式运算环境UUID的初始设计基于网络计算系统UUID,其设计受
Domain/OS 中定义和使用的(64位)唯一标识符的启发,这是一个也由
阿波罗电脑
设计的操作系统。后来,微软视窗平台采用分布式运算环境设计作为全局唯一标识符(GUID)。
2005年7月,RFC 4122 为 UUID 注册了一个 URN
命名空间,并制定了早期的规范。当 RFC 4122
作为互联网工程任务组标准发布时,国际电信联盟基于先前的标准和 RFC 4122
早期版本标准化了 UUID。
uuid格式
UUID由16个字节组成,一共是128位,转换成16进制表示后共有32位,其定义如下:
1 2
| const Size = 16 type UUID [Size]byte
|
一般会使用-来连接其中的各个部分,因此,常见的为36个字符:
1 2 3
| 784b99c1-2a77-11ec-8421-0800270e658d | | 版本 变体
|
在一个UUID中,可以通过上述两个位置得知UUID生成器的版本、以及该版本下的变体版本。UUID现有5个版本,每个版本定义了不同的生成逻辑,因此其安全性和适用场景也有所区别。
| version |
generation rule |
| Version 1 |
date-time and MAC address |
| Version 2 |
date-time and MAC address, DCE security version (removed) |
| Version 3 |
based on MD5 hashing of a named value |
| Version 4 |
random |
| Version 5 |
based on SHA-1 hashing of a named value |
在github.com/gofrs/uuid库中的定义如下,之所以没有v2是因为v2版本已经弃用了。
1 2 3 4 5 6
| type Generator interface { NewV1() (UUID, error) NewV3(ns UUID, name string) UUID NewV4() (UUID, error) NewV5(ns UUID, name string) UUID }
|
uuid:v1
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| func (g *Gen) NewV1() (UUID, error) { u := UUID{}
timeNow, clockSeq, err := g.getClockSequence() if err != nil { return Nil, err } binary.BigEndian.PutUint32(u[0:], uint32(timeNow)) binary.BigEndian.PutUint16(u[4:], uint16(timeNow>>32)) binary.BigEndian.PutUint16(u[6:], uint16(timeNow>>48)) binary.BigEndian.PutUint16(u[8:], clockSeq)
hardwareAddr, err := g.getHardwareAddr() if err != nil { return Nil, err } copy(u[10:], hardwareAddr)
u.SetVersion(V1) u.SetVariant(VariantRFC4122)
return u, nil }
|
从源码可以看出,V1版本的UUID生成基于时间戳和网卡MAC地址,同时设置两个标志位(版本和变体版本)。多次生成后如下:
1 2
| f781c598-2afc-11ec-a8f6-0800270e658d f6cf8996-2afc-11ec-8afb-0800270e658d 第10位开始后面mac部分相同
|
因此,V1版本可以基于uuid反向推出mac网卡地址以及时间戳,并以此推测出后续生成的uuid,所以它并不安全。
uuid.v3
为了解决安全性问题,v3版本采用了MD5的方式计算uuid。通过一个已生成的uuid,结合一个名称进行哈希,最后再设置版本和变体版本。
1 2 3 4 5 6 7 8
| func main() { u1, err := uuid.NewV1() if err != nil { log.Fatalf("failed to generate UUID: %v", err) } u3 := uuid.NewV3(u1, "uuid.v3") fmt.Println(u3, err) }
|
最后生成的效果如下:
1 2
| b31741ed-47ee-34f1-bdd6-a73e7be61f10 66dc196b-ad5e-3e52-ad8d-dd55d5f180bb 除了版本号和变体版本号之外,其他均不相同
|
因此,v3版本通过MD5哈希后相当于给了现有一个uuid再套一层。当然uuid对象本质是一个字节数组,我们也可以自定义一个字节数组作为参数传递。如:
1 2 3 4
| func main() { u3 := uuid.NewV3(uuid.UUID([16]byte{1: 1, 3: 3}), "uuid.v3") fmt.Println(u3) }
|
相同参数下生成的uuid一定是一致的,这也是v3版本的一个特点。其源码比较简单,如下:
1 2 3 4 5 6 7
| func (g *Gen) NewV3(ns UUID, name string) UUID { u := newFromHash(md5.New(), ns, name) u.SetVersion(V3) u.SetVariant(VariantRFC4122) return u }
|
uuid.v4
v4版本的生成依赖随机数,其源码如下:
1 2 3 4 5 6 7 8 9 10
| func (g *Gen) NewV4() (UUID, error) { u := UUID{} if _, err := io.ReadFull(g.rand, u[:]); err != nil { return Nil, err } u.SetVersion(V4) u.SetVariant(VariantRFC4122)
return u, nil }
|
因此,它和平台、硬件、时间都没关系,它也是使用最多的一种uuid生成方式。
uuid.v5
v5版本和v3版本是一样的,只不过v5采用了SHA1算法来计算哈希,而v3采用的是MD5。
1 2 3 4 5 6 7
| func (g *Gen) NewV5(ns UUID, name string) UUID { u := newFromHash(sha1.New(), ns, name) u.SetVersion(V5) u.SetVariant(VariantRFC4122)
return u }
|
如何选择
- v1基于mac地址和时间戳,可反向推导和预测,因此不推荐。
- v3和v5能够实现相同参数产生相同的uuid,因此,在需要这种功能的场景下可以使用。
- v4版本依赖随机数,更适合于大多数业务场景。
- v2版本已经弃用,相比v1,它除了加入mac,还加入了标识符(GID/UID),其暴露的用户信息更多。