惟一識別碼之UUID入門與實戰

上一篇從身份證號是如何生成,來認識了中心機構下生成惟一標識的方法,這一篇來看看面對龐大的計算機世界,無中心機構時,那又如何生成惟一標識呢?javascript


認識UUIDjava

接下來從一個廣爲人知的模塊UUID講起,它彷佛在每一個語言裏都有對應的實現,甚至在部分Unix系統直接提供了實現。node

UUID是什麼?

UUID的全稱是Universally Unique Identifier,中文爲通用惟一識別碼。自己是由一組32位數的16進制數字所構成,故UUID理論上的總數爲16 32=2 128,約等於3.4 x 10 38。也就是說若每納秒產生1兆個UUID,要花100億年纔會將全部UUID使用完,也就是說最多100億年的時間,UUID將一定出現重複,不過100億年地球是否存在也彷佛不肯定,暫時沒必要考慮那麼長遠。

UUID的表現形式

UUID也是須要像身份證號同樣事先制定一些簡單的規則進去的,它的標準型式包含32個16進制數字,以連字號分爲五段,表現形式爲8-4-4-4-12的32個字符,以下所示:

xxxxxxxx-xxxx-Mxxx-Nxxx-xxxxxxxxxxxxpython

其中M與N都有特殊含義,M表示UUID版本,目前只有五個版本,即只會出現1,2,3,4,5,數字 N的一至三個最高有效位表示 UUID 變體,目前只會出現8,9,a,b四種狀況。算法

UUID的版本進化史

1、基於時間和空間的UUID

第一個版本的uuid與身份證號的設計有一點相似,在尋找一箇中心機構來解決惟一性的問題,在互聯網世界裏,時間能夠變得更加精細,與宏觀世界裏相比它能精確到納秒。而空間的控制倒是一個難題,萬維網全球化的前提下,很難找出一個相似於政府的機構來制定統一規範,因此只能從計算機出廠時所帶的惟一編碼(MAC地址)來用做空間上的標識。
理想狀況下,每一臺計算機都有惟一的MAC地址,每一臺計算機在某一時刻執行一次生成UUID的操做,在全球內確定是惟一的。相似於二維世界橫縱線的交匯,若是橫向表明時間,時間永遠不會倒流,縱向表明機器,機器不會在同一時刻執行兩次操做。

可是現實狀況卻並不是是這樣,也從橫縱兩個方向去看。npm

1.計算機對於時間雖然精度很高,可是分佈在世界各個角落裏的狀況下,計算機並不會經過某一箇中心點獲取當前時間,而是根據機器內部自身來獲取,那就會出現一個問題,計算機自身時鐘有誤後被校準出現時間相同後生成uuid的問題,不過通常能夠忽略,時間是相對的概念,只要自身一直保持一個時鐘,便不會出現問題。2.實際上,MAC地址並不是徹底惟一。首先出廠計算機的商家也不是全球一家,即便約定了規範也不能保證網卡製造商沒有偏差地爲網卡分配惟一的 MAC地址。另外計算機在用戶的手裏,MAC地址在用戶計算機上,用戶要是瞭解計算機的原理構造,能不能主動的修改一下MAC地址呢?答案是能夠的。3.同時執行生成UUID程序。當兩個進程同時跑了一段生成UUID的代碼時,它們所處的時間點一致,MAC地址也一致,這時候便也會出現生成相同UUID的狀況。安全

以上從幾個角度去看初版本的UUID生成後的會出現不惟一的緣由,可是上述狀況出現仍是很小几率的,因此基本目前來講最可靠能保證全球的惟一性的實現方法,也由於此,初版本UUID在一些前惟一性場景仍是很是常見。bash


使用示例服務器

Nodejs版本app

我翻閱了一下uuid這一版本的源碼,雖然使用的人很是多,可是實際內部實現並無取機器的MAC地址,由隨機數拼接而成。

const uuidv1 = require('uuid').v1;
const logger = console.log;
logger('uuid v1版本:%s', uuidv1());
// uuid v1版本:10e10f40-bd02-11e9-b241-97aa7a999bec複製代碼

python版本

在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信息,最後在計算機信息中心配合下,肯定其位置並緝拿歸案。

2、基於初版卻更安全的DCE UUID

這一版本的uuid是基於初版本的,首先它也是從時間加空間的角度來生成的,而後又在其之上由將內部實現稍做修改,達到安全的目的。在內部實現中,這一版本的UUID除了時鐘序列的最低有效8 bits 被本地域號替換,而且時間戳的最低有效32 bits 由在指定本地域內有意義的整數標識符替換。
這一版本我找了Nodejs的uuid包,python自帶的uuid等包都沒有相關的實現v2版本,因此沒有辦法找到示例代碼了,看來這一版本的UUID用的人也很是少。

3、基於MD5散列算法的UUID

這一版本的UUID與上面兩個版本出發角度就不相同,我理解是在哈希算法角度出發,當你有相同的輸入時,你就能夠獲得相同的UUID結果。其內部實現有兩個概念, 名字空間和輸入內容,在生成UUID時,先要肯定命名空間,而後將命名空間和輸入的值進行鏈接,最後用 MD5 散列函數進行運算完成。

默認的命名空間

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-838a012a5936複製代碼

python版

import uuid
uuid.uuid3(uuid.NAMESPACE_DNS, "myString")
# UUID('21fc48e5-63f0-3849-8b9d-838a012a5936')複製代碼

4、基於隨機數的UUID

這個版本的UUID是使用最多的,它的本質是根據隨機數或者僞隨機數來生成UUID,最大的問題就是這種重複率的問題,這一類型的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);
}複製代碼

5、基於SHA1散列算法的UUID

這個版本與第三版本的UUID相似,但使用的散列算法不一樣 ,它利用SHA1 代替了 MD5,其他和第三個版本同樣,可是相比於第三步版 更加推薦使用這一版本。


SHA1和MD5的區別

首先它們兩個都是散列函數,對於SHA1來講,長度小於2^64位的消息,則會產生一個160位的消息摘要,而MD5最顯著和最重要的區別是它的摘要比SHA1摘要少32 位,它只產生出一個128位的消息摘要,若是使用強行破解技術,SHA-1相比於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-52a7df8164ec複製代碼

python版本

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/通用標識碼


如上內容均爲本身總結,不免會有錯誤或者認識誤差,若有問題,但願你們留言指正,以避免誤人,如有什麼問題請留言,會盡力回答之。若是對你有幫助不要忘了分享給你的朋友或者點擊右下方的「在看」哦!也能夠關注做者,查看歷史文章而且關注最新動態,助你早日成爲一名全棧工程師!

相關文章
相關標籤/搜索