1. 程式人生 > >證明與計算(5): 從加密哈希函數到一致性哈希

證明與計算(5): 從加密哈希函數到一致性哈希

算法 eric 開放定址法 0x03 safe 怎樣 scala 多少 eml

目錄:

** 0x01 [哈希函數] vs [加密哈希函數]
** 0x02 [哈希碰撞] vs [生日問題]
** 0x03 [哈希表] vs [分布式哈希表]
** 0x04 [歐式距離] vs [三角不等式]
** 0x05 [異或距離] vs [前綴路由表]

0x01 [哈希函數] vs [加密哈希函數]

在哈希表計算索引的時候,我們需要一個哈希函數,通過hash(key)來計算key在哈希表裏的index。這個地方的哈希函數只要盡量滿足均勻分布,周期盡量大,計算速度又足夠快等即可。而在密碼學裏用的比較多的,則是加密哈希函數。[加密哈希函數],它需要盡量滿足三個安全性質:

  1. 原像防禦(Pre-Image resistance)
    :就是任意給一個hash值h,你很難破解出它的原像m,使得hash(m)=h,這個難度一般是NP難度的,也就是所謂的單向函數。
  2. 弱碰撞防禦(Second pre-image resistance):就是給定一個m1,你很難找到另一個m2,使得h(m1)=h(m2)。
  3. 強碰撞防禦(Collision resistance):就是你很難找到兩個不同的m1和m2,使得h(m1)=h(m2)。
    如果符合強碰撞防禦,也就是會符合弱碰撞防禦,但是不一定符合原像防禦。如果只滿足前2個,在密碼學上是不安全的,一般加密哈希函數應該滿足1,2,3。

常見的密碼哈希函數有:

  1. SHA1, SHA2(SHA-256,SHA-512), SHA3(SHA3-256, SHA3-512, Keccak-256, Keccak-512)
  2. MD5
  3. RIPEMD-160

其中,SHA-1因為已經在2005年可以被暴力找出碰撞,就不滿足性質3,從而已經不再推薦使用了,現在常用的SHA-256,SHA-512都是屬於SHA-2這個大類的,最新的則是SHA-3系列。MD5也已經在2004年就不滿足強碰撞防禦了,也不推薦作為密碼哈希函數來用了。

加密哈希函數的使用場景,典型的有在SSL(Secure Sockets Layer)和數字簽名(Digital Signature)上使用。

[1] Cryptographic hash function
[2] SHA-1
[3] SHA-2
[4] SHA-3
[5] MD5
[6] RIPEMD

0x02 [哈希碰撞] vs [生日問題]

一個哈希函數的取值空間是有限的,那麽所有可能的結果個數是固定的,假設為D。那麽,如果你能計算D+1次,自然一定有兩個值發生碰撞。好在一般情況下,D都是很大的,有限算力的計算機在有生之年是沒希望通過這個方式直接暴力計算出碰撞值。但是,如果我們退而求其次,只關心能否有50%的概率暴力計算出碰撞值呢?那麽,你需要計算多少次後,才會有50%的概率發生碰撞?

這個問題,實際上叫做“生日問題”。在一個班級裏有n個同學,每個同學的生日都只能是D=365天中的一天,那麽班級裏有兩個人生日相同的概率P是多少呢?我們可以很容易計算出班級裏所有人概率都不相同的概率P‘。計算方法是,依次計算第i個同學與前面(i-1)個同學生日都不相同的概率。那麽:

  1. 第1個人與第0個人生日不相同的概率是1。
  2. 第2個人與第1個人生日不相同的概率是 364/365。
  3. 同理,第3個人與第2個人生日不相同的概率是363/365。
  4. ...
  5. 第n個人與前面n-1個人生日不相同的概率是(365-n+1)/365

從而,P‘=1x{364/365}x{363/365}x...x {(365-n+1)/365},做一個簡單變換後得到:

P‘= 1x{1-1/365}x{1-2/365}x...x {1-(n-1)/365}

這樣,至少有2個人生日相同的概率P = 1-P‘。推廣一下,把365替換為D,則有:

P‘= 1x{1-1/D}x{1-2/D}x...x {1- (n-1)/D }

對於哈希函數來說,一般D都是很大的,例如SHA-256,可能的D的取值有2^256個,那麽這個時候1/D就很小,我們可以利用e^x的泰勒公式的性質來規約P‘。具體做法是:

  1. 做泰勒展開,e^x = 1+x+x^2/2+x^3/6+x^4/24+....
  2. 因此,當x很小的時候,e^x就可以直接用1+x來近似計算。
  3. 則,如果x=-i/D,由於D很大,-i/D就很小,從而可以直接用 e^{-i/D}=1-i/D 替換。

那麽,就得到了如下的公式:
P‘= 1 x e^{-1/D} x e^{-2/D} ... x e^{-(n-1)/D}
= e^{-(1+2+...+n-1)/D}
= e^{ - n x (n-1)/2D }

進一步近似,用n^2代替 n x (n-1) 則有

  1. P‘= e^{- n^2/2D }
  2. P = 1-P‘ = 1-e^{-n^2/2D}

再次利用當x很小時 e^x=1+x 的近似公式,就得到最後的近似值:

P = 1-e^{-n^2/2D} = 1 - (1-n^2/2D) = n^2/2D

有了這個公式,如果反算:“班級有多少人時,至少有兩個人生日相同的概率是50%”,或者“哈希函數計算多少次後,有兩個值碰撞的概率是50%”,我們只需要反求n即可:

n = sqrt(2D x P),把P=0.5帶入,得 n = sqrt(D).

[1] Birthday problem
[2] 生日問題
[3] Birthday attack
[4] 生日攻擊
[5] Collision attack

0x03 [哈希表] vs [分布式哈希表]

回頭說兩個跟哈希相關的數據結構。哈希表(Hash table)也稱散列表,通過哈希函數把key映射到哈希表裏的桶(bucket)的地址。由於哈希表的長度有限,通常哈希函數不可避免會發生碰撞,根據碰撞時的處理方式不同,哈希表分為兩種:

  1. 開鏈法(Seperate Chaining)處理哈希碰撞。簡單說把所有key碰撞的,用一個鏈表串起來,當然你也可以用二叉樹串起來以進一步改進查詢速度。
  2. 開放定址法(Open addressing, 又叫閉散列法,Closed hashing)。簡單說遇到沖突時,直接通過規則在哈希表裏找一個其他桶來用,例如使用線性探測法確定新位置:index=Hash(key)+ i

對於開鏈法哈希表,需要哈希函數滿足均勻分布,也就是哈希函數會盡量把key均勻地映射到哈希桶上。而對於開放定址法,哈希函數還要盡量減少聚集(cluster)效應導致局部哈希桶太密集,因為這會導致插入和搜索到時間變大。

相對於只考慮單機程序的哈希表,分布式哈希表(Distrubuted hash table, DHT)則構建在P2P網絡上。分布式哈希表由P2P網絡的節點組成,每個節點都會維護好Key被映射到的節點信息,從而實現在P2P網絡裏對Key-Value進行添加、查找、刪除的能力。一個好的DHT需要滿足下面三個性質:

  1. 自治和去中心化(Autonomy and Decentralization)。節點之間的相互發現、加入、離開都不依賴於中心節點。
  2. 錯誤容忍性(Fault tolerance)。幾點可以自由加入、離開、移除,而整個系統依然可用。
  3. 伸縮性(Scalability)。系統的效率不因節點的增加而減低,即使增加成千上萬節點也是一樣。

DHT的結構是怎樣的呢?

首先從DHT的兩個基本概念入手,是最佳的方式:

  1. keyspace。例如要存儲一個資源,資源的名字通過SHA-160做1次哈希,得到160-bit的key,所有這樣的key構成了keyspace。
  2. Overlay network。簡單說就是一個P2P網絡,但是這些P2P網絡裏的節點ID需要經過設計。

在理解DHT和通常的P2P網絡有什麽區別之前,先看下在DHT網絡上會核心做的兩個操作:

  1. put(key,value)。動態查找到應該持有該key的Node,通過P2P網絡把該key-value寫入到那個Node。
  2. get(key)。動態查找到應該持有該key到Node,通過P2P網絡把那個key-value讀出來。

上面,核心的兩個動作是:

  1. 計算應該持有該key的Node
  2. 動態查找該Node

計算應該持有該key的Node有好多種算法,其中常見的一種做法就是使用一致性哈希。一致性哈希的核心做法是:

  1. 讓Node的ID,和KeySpace中的Key,采用一致的格式和長度。
  2. 找到一種合適的距離計算函數D(distance)。
  3. 距離函數可以計算兩個key之間的距離:D(key1,key2)。
  4. 距離函數也可以計算一個key和一個Node的ID之間的距離:D(key,ID)。
  5. 這種距離計算函數跟節點之間的物理距離沒有關系。
  6. 根據距離,所有的key和所有ID之間就有一個排序關系: ...,ID(i),keyi1,...,keyim,ID(i+1)
  7. 所有排序在ID(i)和ID(i+1)之間的key,都歸屬於ID(i+1)。

而動態查找Node實際上就是一個貪心算法:

  1. 每個Node有一個路由表,路由表裏包含了離自己從近到遠的節點列表。
  2. 在路由表裏查找key所在Node,
  3. 如果從路由表裏直接到key所在的Node,就通過P2P網絡與之通信。
  4. 否則從路由表裏找到離key最近的Node,轉發給那個離key最近的Node去查找。

具體關於如何“計算距離”,以及如何“設計路由表”,在Kademlia網絡裏有更具體的做法,下一次可以單獨來講。

[1] Hash table
[2] 哈希表
[2] Consistent hashing
[3] 一致性哈希
[4] Distributed hash table(DHT)
[5] Kademlia
[6] Overlay network

0x04 [歐式距離] vs [三角不等式]

理解分布式哈希表,首先需要理解分布式哈希表裏是如何計算

  1. 兩個Key之間的距離
  2. Key和Node之間的距離

在介紹DHT裏的距離之前,我們先看看歐幾裏得空間中的點是如何計算距離的。

在三維空間中,計算兩個點之間的距離,叫做歐幾裏得距離。給定P1(x1,y1),P2(x2,y2),那麽P1,P2之間的距離是sqrt(|x1-x2|^2+|y1-y2|^2)。對坐標差絕對值求平方和再開方,這個叫做2-範數距離。

有2-範數距離,當然也就有1-範數距離,公式是|x1-x2|+|y1-y2|。實際上1-範數距離也叫做“曼哈頓距離(Manhattan distance)”或者“出租車距離(Taxicab distance)”,簡單說一輛出租車在曼哈頓裏從一個點到另一個點,需要一直繞著格子建築物走格子才能到達另一個點。
技術分享圖片

更一般的p-範數距離,對坐標差絕對值,先求p次方,再開p次方。也就是(|x1-x2|^p+|y1-y2|^p)^{1/p}。如果p趨向於無窮大,求極限就是無窮範數距離,也叫車比雪夫距離,國際象棋上王從一個位置要走到另一個位置所需要的步數就是車比雪夫距離。
技術分享圖片

進一步如果把P放在N維空間中,也可以用同樣的方式求這些N維歐幾裏得空間中點的p-範數距離,此時它們統一被叫做明可夫斯基距離(Minkowski)。

有這麽多種不同的距離,那麽是不是兩個點之間的任何函數都可以做為這兩個點之間的一種“合法”的距離呢?顯然不是,一個函數D要能做為歐式空間中兩個點之間的距離,必須滿足下面三個性質。

  1. 正性:D(P1,P2)>= 0,當且僅當P1=P2時才為0,重合的點距離為0,其他為正。
  2. 對稱性:D(P1,P2) = D(P2,P1),距離和方向沒有關系。
  3. 三角不等式:D(P1,P3) <= D(P1,P2) + D(P2,P3),兩點之間直線最短。

其中最突出的是三角不等式。實際上p-範數距離裏面,p不一定要求是整數,但是如果p<1,會導致三角不等式不成立,因此p-範數要求p>=1。

有了距離,我們關心的是,可以得到下面3個DHT需要的功能:

  1. 計算P與P1,P2,P3,...之間的距離
  2. 根據距離遠近,判定P在哪個區間,例如P在P_i, P_{i+1}之間
  3. 那麽,我們可以讓P歸P_{i+1}管理。

但是DHT並不是直接使用p-範數距離,而是巧妙使用了異或(XOR)這個位操作的能力。待續。

[1] distance
[2] Minkowski distance
[3] Manhattan distance
[4] 曼哈頓距離
[5] Chebyshev distance
[6] 車比雪夫距離

0x05 [異或距離] vs [前綴路由表]

編程中,在位上的操作實際上並不難理解。常見的位操作包括與(AND)、或(OR)、非(NOT)、異或(XOR),位移等。其中XOR的使用相對較少一些,但是如果理解了XOR的語義之後,就會發現XOR在操作數據方面有很大用途。

我們將一個黑白棋子保持原狀看做0,將一個棋子翻轉到另一面看做1,給你兩次機會選擇翻轉或者不翻轉。那麽有下面幾種組合:

  1. 不翻轉,不翻轉,結果等於不翻轉。
  2. 不翻轉,翻轉,結果等於翻轉。
  3. 翻轉,不翻轉,結果等於翻轉。
  4. 翻轉,翻轉,結果等於不翻轉(翻了兩次,等於沒翻轉)。

把不翻轉記為0,翻轉記為1,這就是0和1的“異或”運算:
0 ⊕ 0 = 0, 0 ⊕ 1 = 1, 1 ⊕ 0 = 1, 1 ⊕ 1 = 0.

異或(XOR)在對稱密碼學中起著“基本操作”的作用。例如,假設明文是a,密鑰是b,那麽最簡單的加密和解密操作分別是:

  1. 加密:c = a xor b,相當於對a的每個bit,用b對應的bit去做了一次翻轉操作。
  2. 解密:a = c xor b = a xor b xor b,相當於對a的每個bit,用b對應的bit去做一次翻轉,再用b的每個bit去做一次翻轉,根據上面的翻轉規則,兩次翻轉等於沒有翻轉,所以結果等於a。

理解到翻轉這個意思後,就可以活學活用了。例如,考慮一個趣題:“如何不使用臨時變量交換兩個變量a和b?” 這個題目有不只一種做法,先看一個只用加減法的做法:

  1. a = a - b
  2. b = a + b
  3. a = b - a

現在,我們決定用“加解密”的方式來解決這個問題:

  1. a = a xor b,可以看作是:“使用b對a加密”或“使用a對b加密”,此時a等於密文
  2. b = a xor b,使用原始的b對密文解密,得到原始的a
  3. a = a xor b,此時b等於原來的a,則a xor b等於使用原始的a對密文解密,得到原始的b

通過上述的幾個小步驟,我們對xor的妙用有了一定的理解。回過頭來,我們看下異或距離(XOR distance)這個概念。簡單說,兩個長度相同的二進制數據之間的異或操作,可以構成一個距離函數。也就是異或操作滿足距離函數的三個重要性質:

  1. 正性:a xor b >= 0,當且僅當a=b時取0。很好理解,a xor a,每個bit上的值都相等,“翻轉+翻轉”或者“不翻轉+不翻轉”結果都等於0.
  2. 對稱性:a xor b = b xor a。
  3. 三角不等式:a xor c <= a xor b + b xor c

有了異或距離,就可以解決P2P網絡上如何把數據項(Key)存放到哪些節點(Node)的問題。一個簡化的做法如下:

  1. 節點的ID,和數據的鍵Key,使用同樣的格式和長度。
  2. 每個節點持有由遠到近的一系列其他節點ID列表。
  3. 從其他節點列表裏查找與Key最近的一些節點,計算當前最近距離min-d,把<key,min-d,sessionid>發送給距離最近的節點。
  4. 那些距離最近的節點收到後,在自己的節點列表裏再次查找,如果不能找到比min-d更近的節點,就算查找成功。
  5. 讀或者寫入數據。sessioid用來做去重等。

Node和Key構成的一致性哈希結構如圖所示:
技術分享圖片

其中,比較關鍵的是“每個節點持有由遠到近的一系列其他節點ID列表” 這個過程是如何做的。通過舉例的方式,先假設節點ID如下:

  1. 假設Node的ID是3-bit的。
  2. 所有可能的節點ID有8個:000,001,010,011,100,101,110,111
  3. 假設011不是一個節點,剩下7個節點:000,001,010,100,101,110,111

現在第6個節點是110,標記為Node(6),,我們觀察110節點上是如何由近到遠持有其他節點ID。

  1. 分配3個節點列表list1,list2,list3,之所以是3個是因為節點ID長度是3.
  2. 每個列表最多存3個ID。
  3. 第i個列表裏的每個ID第i個比特和Node(6)的第i個比特必須不同。
  4. 第i個列表裏的每個ID前i-1個比特和Node(6)的前i-1個比特必須相同。

根據規則3和規則4,Node(6)持有的三個list分別是:

  1. list1: [000, 001, 010]。可以看到第1個比特都是0,和110的第1個比特不同。
  2. list2: [100, 101]。可以看到第2個比特都是0,和110的第2個比特不同;同時,第1個比特都是1,和110的第1個比特相同。
  3. list3: [111]。可以看到第3個比特是1,和110的第3個比特不同;同時前2個比特都是11,和110的前2個比特相同。

這三個list如圖中圓圈所示:
技術分享圖片

根據規則3和規則4,在計算異或距離的時候:

  1. list1裏面的ID必須一個比特和當前ID不同,理論上可以存儲1/2的其他節點ID。
  2. list2裏面的ID第1個比特必須和當前ID的第1個比特相同,第2比特必須不同,異或距離計算出來的值第1個比特必然是0。理論上可以有1/4的其他節點ID。
  3. list3裏面的ID前2個比特必須和當前ID的前2個比特相同,第3比特必須不同,異或距離計算出來的值前2個比特必然是0。理論上可以有1/8的其他節點ID。
  4. ...

可以看到,每個列表裏存儲到其他節點確實在異或距離上是越來越近。這就是異或距離在DHT一致性哈希裏的妙用。當限制每個list的容量為k時,可以動態根據節點的連通性維持每個list裏的k個節點。

觀察每個listi裏的節點都有i-1個前綴相同,它們構成了一個前綴路由表。

[1] XOR
[2] 異或
[3] Kademlia, A P2P DHT
[4] xor distance, js code
[5] xor distance and routing table

--end--

證明與計算(5): 從加密哈希函數到一致性哈希