通用唯一识别码(英语: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位,其定义如下:
const Size = 16
type UUID [Size]byte
一般会使用-
来连接其中的各个部分,因此,常见的为36个字符:
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版本已经弃用了。
type Generator interface {
NewV1() (UUID, error)
NewV3(ns UUID, name string) UUID
NewV4() (UUID, error)
NewV5(ns UUID, name string) UUID
}
uuid:v1
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地址,同时设置两个标志位(版本和变体版本)。多次生成后如下:
f781c598-2afc-11ec-a8f6-0800270e658d
f6cf8996-2afc-11ec-8afb-0800270e658d 第10位开始后面mac部分相同
因此,V1版本可以基于uuid反向推出mac网卡地址以及时间戳,并以此推测出后续生成的uuid,所以它并不安全。
uuid.v3
为了解决安全性问题,v3版本采用了MD5的方式计算uuid。通过一个已生成的uuid,结合一个名称进行哈希,最后再设置版本和变体版本。
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)
}
最后生成的效果如下:
b31741ed-47ee-34f1-bdd6-a73e7be61f10
66dc196b-ad5e-3e52-ad8d-dd55d5f180bb 除了版本号和变体版本号之外,其他均不相同
因此,v3版本通过MD5哈希后相当于给了现有一个uuid再套一层。当然uuid对象本质是一个字节数组,我们也可以自定义一个字节数组作为参数传递。如:
func main() {
u3 := uuid.NewV3(uuid.UUID([16]byte{1: 1, 3: 3}), "uuid.v3")
fmt.Println(u3)
}
相同参数下生成的uuid一定是一致的,这也是v3版本的一个特点。其源码比较简单,如下:
func (g *Gen) NewV3(ns UUID, name string) UUID {
// 直接计算 md5(ns + name)
u := newFromHash(md5.New(), ns, name)
u.SetVersion(V3)
u.SetVariant(VariantRFC4122)
return u
}
uuid.v4
v4版本的生成依赖随机数,其源码如下:
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。
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),其暴露的用户信息更多。