上一篇从身份证号是如何生成,来认识了中心机构下生成唯一标识的方法,这一篇来看看面对庞大的计算机世界,无中心机构时,那又如何生成唯一标识呢?
认识UUID
接下来从一个广为人知的模块UUID讲起,它似乎在每个语言里都有对应的实现,甚至在部分Unix系统直接提供了实现。
UUID是什么?
UUID的表现形式
xxxxxxxx-xxxx-Mxxx-Nxxx-xxxxxxxxxxxx
其中M与N都有特殊含义,M表示UUID版本,目前只有五个版本,即只会出现1,2,3,4,5,数字 N的一至三个最高有效位表示 UUID 变体,目前只会出现8,9,a,b四种情况。
UUID的版本进化史
一、基于时间和空间的UUID
但是现实情况却并非是这样,也从横纵两个方向去看。
1.计算机对于时间虽然精度很高,但是分布在世界各个角落里的情况下,计算机并不会通过某一个中心点获取当前时间,而是根据机器内部自身来获取,那就会出现一个问题,计算机自身时钟有误后被校准出现时间相同后生成uuid的问题,不过一般可以忽略,时间是相对的概念,只要自身一直保持一个时钟,便不会出现问题。2.实际上,MAC地址并非完全唯一。首先出厂计算机的商家也不是全球一家,即使约定了规范也不能保证网卡制造商没有误差地为网卡分配唯一的 MAC地址。另外计算机在用户的手里,MAC地址在用户计算机上,用户要是了解计算机的原理构造,能不能主动的修改一下MAC地址呢?答案是可以的。3.同时执行生成UUID程序。当两个进程同时跑了一段生成UUID的代码时,它们所处的时间点一致,MAC地址也一致,这时候便也会出现生成相同UUID的情况。
以上从几个角度去看第一版本的UUID生成后的会出现不唯一的原因,但是上述情况出现还是很小概率的,所以基本目前来说最可靠能保证全球的唯一性的实现方法,也因为此,第一版本UUID在一些前唯一性场景还是非常常见。
使用示例
Nodejs版本
我翻阅了一下uuid这一版本的源码,虽然使用的人非常多,但是实际内部实现并没有取机器的MAC地址,由随机数拼接而成。
const uuidv1 = require('uuid').v1;
const logger = console.log;
logger('uuid v1版本:%s', uuidv1());
// uuid v1版本:10e10f40-bd02-11e9-b241-97aa7a999becpython版本
在python自带的uuid模块中,确实获取了机器网卡的MAC地址。
import uuid;
uuid.uuid1();
# UUID('e852b72e-ba4d-11e9-8e8e-acde48001122')用ifconfig命令查看一下网卡MAC。

从上两个例子可以均可看出M位是1,N位在a,b,8,9内,都是符合UUID开始时所述的规范。最后的12位acde48001122正是我机器的网卡,一直保持不变的。
暴露MAC地址所产生的安全问题
这一版本的UUID比较大的一个问题就在于它的组成里含有用户的MAC地址,每台计算机绑定了一个用户,则MAC地址也对应了用户,这代表着MAC地址的暴露则造成了隐私问题与安全问题。
通过UUID抓获病毒制造者
1998年,由美国人David L. Smith运用Word的宏运算编写出的一个电脑病毒,其主要是通过邮件传播,邮件的标题通常为“这是给你的资料,不要让任何人看见”,一旦收件人打开邮件,病毒就会自动向用户通讯录的前50位好友复制发送同样的邮件。尽管这种病毒不会删除电脑系统文件,但它引发的大量电子邮件会阻塞电子邮件服务器,使之瘫痪,造成了相当大的危害,最终就是这位病毒制造者David L. Smith就是因为在脚本中使用的UUID中暴露了机器的MAC信息,最后在计算机信息中心配合下,确定其位置并缉拿归案。
二、基于第一版却更安全的DCE UUID
三、基于MD5散列算法的UUID
默认的命名空间
nodejs中
// nodejs uuid源码中预定义的命名空间
generateUUID.DNS = '6ba7b810-9dad-11d1-80b4-00c04fd430c8';
generateUUID.URL = '6ba7b811-9dad-11d1-80b4-00c04fd430c8';python中:
#python中默认预定义的命名空间
import uuid
uuid.NAMESPACE_DNS #UUID('6ba7b810-9dad-11d1-80b4-00c04fd430c8');
uuid.NAMESPACE_URL #UUID('6ba7b811-9dad-11d1-80b4-00c04fd430c8');
uuid.NAMESPACE_X500 #UUID('6ba7b811-9dad-11d1-80b4-00c04fd430c8');
uuid.NAMESPACE_XX #UUID('6ba7b811-9dad-11d1-80b4-00c04fd430c8');版本特点:
1. 基于相同命名空间下,不同输入值的生成的UUID不同,并非完全不同,有一定几率相同。
2. 基于相同命名空间下,相同输入值的生成的UUID不同。
3. 基于不同命名空间生成的UUID一定不会相同,当然我理解这是不出现MD5碰撞的前提下。
4. 基于两个输入值的UUID相同,那么一定是来自相同的命名空间下的同一个输入值。
使用示例
Nodejs版本
const uuidv3 = require('uuid/v3');
const logger = console.log;
logger('uuid v3版本:%s', uuidv3('myString', uuidv3.DNS))
// 21fc48e5-63f0-3849-8b9d-838a012a5936python版
import uuid
uuid.uuid3(uuid.NAMESPACE_DNS, "myString")
# UUID('21fc48e5-63f0-3849-8b9d-838a012a5936')四、基于随机数的UUID
一个 比较不错的基于JavaScript的实现。
function uuidv4() {
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8);
return v.toString(16);
});
}
使用示例
Nodejs版本
const uuidv4 = require('uuid/v4');
const logger = console.log;
logger('uuid v4版本:%s', uuidv4())python版本:
import uuid
uuid.uuid4()# UUID('1a9e40e2-3862-41d4-bd4e-0dd928e81055')Nodejs的UUID v4版源码分析
nodejs的uuid包中,v4版本实现比较简单,大家也可以去翻阅查看。我这里删减一部分代码,将主干留下来讲解。
// randomBytes的官方定义:生成加密的强伪随机数据。size参数是一个数字,指示要生成的字节数。
// 这里生成16字节数强伪随机数,返回类型为buffer的数据。
var rng = require('crypto').randomBytes(16);
// 将byte生成uuid 的 string的工具函数
function bytesToUuid(buf) {}
// 主干代码
module.exports = function v4() {
var rnds = rng();
// 位运算符&:两个数值的个位分别相与,同时为1才得1,只要一个为0就为0。
// 位运算符|:两个位只要有一个为1,那么结果都为1。否则就为0
// 将UUID的M和N位进行处理,处理后M位为4,N为a,b,8,9内的任意值
rnds[6] = (rnds[6] & 0x0f) | 0x40;
rnds[8] = (rnds[8] & 0x3f) | 0x80;
return bytesToUuid(rnds);
}五、基于SHA1散列算法的UUID
SHA1和MD5的区别
在Nodejs的uuid的实现中,V5与V3实现唯一不一致的就是散列函数不同。
// v3版本
crypto.createHash('md5').update(bytes).digest();
// v5版本
crypto.createHash('sha1').update(bytes).digest();使用示例
Nodejs版本
const uuidv5 = require('uuid/v5');
const logger = console.log;
logger('uuid v5版本:%s', uuidv5('hello.example.com', uuidv5.DNS))
// uuid v5版本:fdda765f-fc57-5604-a269-52a7df8164ecpython版本
import uuid
uuid.uuid5(uuid.NAMESPACE_DNS, "hello.example.com")
#UUID('fdda765f-fc57-5604-a269-52a7df8164ec')References
[1] Nodejs的uuid:https://www.npmjs.com/package/uuid
[2] 维基百科:https://zh.wikipedia.org/wiki/通用标识码如上内容均为自己总结,难免会有错误或者认识偏差,如有问题,希望大家留言指正,以免误人,若有什么问题请留言,会尽力回答之。如果对你有帮助不要忘了分享给你的朋友或者点击右下方的“在看”哦!也可以关注作者,查看历史文章并且关注最新动态,助你早日成为一名全栈工程师!
