哈希函數
摘要性:在最基本的層面上,一個哈希函數需要將輸入的一個長的信息映射到一個較短的信息上。
碰撞性:即兩個不同的輸入映射到同一個摘要上。如果兩個不同的輸入 x 和 x‘ 有H(x)=H(x'),則稱 x 和 x’ 發生了碰撞。
抗碰撞性:如果對於任何一個PPT上的敵手找到一個碰撞是無法實現的,則稱這個函數具有抗碰撞性。
通常討論抗碰撞性都是在函數定義域大於值域的情況下的,這種情況下碰撞必然是存在的,但是一般很難以找到碰撞。
哈希函數的定義:一個哈希函數(輸出長度為 l )由兩個PPT上的算法(Gen, H)構成
- Gen:一個概率算法,以一個安全參數1n作為輸入,然后輸出密鑰 s ,假設安全參數 1n 隱含在s中。
- H:將一個密鑰 s 以及一個01串 x ∈{0, 1}*作為輸入,然后輸出Hs(x)∈{0, 1}l(n),其中的 n 是隱含在 s 中的安全參數。
如果 H 是定義在輸入固定為 x∈{0, 1}l'(n)並且l'(n)>l(n),則稱這種哈希函數是一個固定長度的哈希函數,並且稱算法 H 為壓縮函數。
這了需要注意的是哈希函數中的密鑰與普通對稱加密方案中的密鑰的區別:
- 並不需要保證所有的輸入都能映射到一個有效的輸出(比如某些哈希函數是定義在一個確定的密鑰 s 上的),並且因此 s 由算法Gen生成,而不是像加密算法中那樣隨機的選擇。
- 哈希函數中的密鑰並不需要保證其機密性,而且當討論一個哈希函數的抗碰撞性時是保證敵手在獲取了密鑰后還是無法找一對碰撞。
抗碰撞性:如果一個PPT上的敵手找到一對碰撞的概率小於某個可忽略函數則稱這種哈希函數是具有抗碰撞性的。
不帶密鑰的哈希函數:在實際應用中使用的密碼哈希函數通常都是有一個固定的輸出長度,並且是不帶密鑰的(也就是說哈希函數只有一個固定的函數H:{0,1}*→{0,1}l(n))。而算法中總是有一個輸出碰撞的常量時間算法,用於輸出硬編碼到算法本身的一對碰撞。一般來說,碰撞對都是未知的並且計算上很難找到的。
哈希函數的安全性:
抗第二原像:給定一個 s 以及一個隨機的 x ,對於一個PPT上的敵手計算上很難求出 x' ≠ x 滿足Hs(x')=Hs(x)。
抗原像:給定一個 s 以及一個隨機的 y ,對於一個PPT上的敵手計算上很難求出一個滿足Hs(x)=y的 x 值。
破解的難度:抗原像 > 抗第二原像 > 抗碰撞
哈希函數定義域擴展
Merkle-Damgard轉換是一種常見的用於擴展哈希函數定義域的方法,用於將壓縮函數擴展為成能夠滿足需求的哈希函數,並且同時保持前者的抗碰撞特性。
因此在設計抗碰撞的哈希函數的時候,一般都着重設計固定長度的哈希函數,也就是說壓縮一個比特的輸入與壓縮任意長度的輸入的難易程度是相同的。
Merkle-Damgard轉換:設(Gen, h)是一個固定長度的哈希函數,其輸入長度為 2n 而輸出長度為 n,則可以構造一個哈希函數:
- Gen:不變
- H:對於一個輸入密鑰 s 以及一個01串 x∈{0, 1}*,x的長度 L 小於2n,則:
- 設B := ceil( L/n ) ,其實B就是分組的個數,在x末尾填充0使 L 為n的倍數,然后將 x 分成 B 份長度為 n 的分組x1, x2, ..., xB。然后設xB+1 := L,其中 L 被編碼成 n 比特的01串。
- 設z0 := 0n (z0也被稱為初始向量),初始向量是任意的,並且可以被替換成其他任何一個常量。
- 對 i = 1, 2, ..., B+1,計算zi := hs(zi-1||xi)
- 最后輸出zB+1
其實意圖如下:

定理:如果(Gen, h)是抗碰撞的,那么(Gen, H)也是抗碰撞的
證明.
設 x 和 x‘ 是兩個不同的輸入,它們的長度分別為L和L',則可以證明對於任意的密鑰 s ,在Hs中的碰撞都能找到hs中的碰撞。
令,Hs(x) = Hs(x')
設x1, ..., xB是 x 的分組,x'1, ..., x'B'是 x’ 的分組,則有兩種情況:
L≠L' :Hs(x) = zB+1 = hs(zB||L), Hs(x') = z'B'+1 = hs(z'B'||L')
因為Hs(x) = Hs(x'),所以hs(zB||L) = hs(z'B'||L'),而L≠L',所以zB||L和z'B'||L'必然是兩個不同的輸入,而這就是hs中的一對碰撞。
L=L‘:則B=B'
設 Ti = zi-1||xi,即hs的第 i 次輸入,令TB+2=zB+1;同樣的定義T'1, ..., T'B+2為計算Hs(x')過程中每次hs的輸入。
設N是滿足TN≠T'N的最大值,因為 x≠x’,而L=L‘,所以N必然是存在的。
因為TB+2 = zB+1 = Hs(x) = Hs(x') = z'B+1 = T'B+2,所以N≤B+1。
當N=B+1時,hs(TN) = zB+1 = Hs(x) = Hs(x') = z'B+1 = hs(T'N),則因為TN≠T'N,所以TN和T'N是hs中的一對碰撞。
用哈希函數驗證任意長度消息完整性
在前面構造任意長度的MAC時,講述了兩種方法,一種是常規的構造方法,第二種是基於為隨機函數的CBC-MAC。而這里介紹的hash-and-MAC,是另一種一種基於抗碰撞哈希函數的方法。
Hash-and-MAC構造方法:設Π=(Mac, Vrfy)是一個對於輸入長度為l(n)的消息驗證碼,設ΠH=(GenH, H)是一個輸出長度為 l(n) 的哈希函數
- Gen':對於輸入的安全參數1n,隨機的選擇 k∈{0, 1}n,然后運行GenH(1n)來獲取s,最終輸出k' := <k, s>
- Mac':對於輸入的密鑰<k, s>以及一個任意長度的消息m∈{0, 1}*,輸出 t ← Mack(Hs(m))
- Vrfy':對於輸入的密鑰<k, s>以及一個任意長度的消息m∈{0, 1}*,以及一個MAC標簽 t ,當且僅當Vrfyk(Hs(m),t)=1的情況下才輸出 1。
Hash-and-MAC的示意圖如下:

定理:如果Π是一個對於長度為 l 的消息安全的MAC,ΠH是一個抗碰撞的哈希函數,則HMAC也是一個對任意長度消息安全的MAC。
證明.
hash-and-MAC的一種特定的實例被稱為HMAC
HMAC的構造方法:設(GenH, H)是一個由(GenH, h)經過Merkel-Damgrad轉換而來的對於長度為n+n'輸入的哈希函數。設ipad和opad是兩個固定常量
- Gen:對於輸入1n,運行GenH(1n)來獲取密鑰s,然后k∈{0, 1}n',最后輸出密鑰<s, k>
- Mac:對於輸入的密鑰<s, k>以及一個消息m∈{0, 1}*,輸出t := Hs( (k⊕opad) || Hs(k⊕ipad)||m )
- Vrfy:對於輸入的密鑰<s, k>以及一個消息m∈{0, 1}*,以及一個MAC標簽 t ,當且僅當 t = Hs( (k⊕opad) || Hs(k⊕ipad)||m )的時候才輸出1。
HMAC是一種工業標准並且在實際中得到了廣泛的應用。它是高效且易於實現的,並且由一個基於對實際哈希函數的假設的安全性的證明來支持其安全性。
哈希函數的輸出長度下限
對於哈希函數的某些攻擊方式的存在,意味着哈希函數H的輸出長度存在着一個下限從而保證其安全性。
尋找碰撞的生日攻擊(Birthday attack):
設H: {0, 1}*→{0, 1}l是一個哈希函數
一個普通的在O(2l)時間內尋找碰撞的方式:直接計算對於H的2l+1個不同的輸入所產生的輸出,因為輸出的長度為l,那么必然能夠找到一個碰撞。
對上面個的那種尋找碰撞的方法進行歸納,可以選擇 q 個不同的x1, ..., xq,計算yi := H(xi),然后檢查yi中是否存在兩個相等的值。為了分析當q<2l時找到碰撞的概率,可以將H看作一個偽隨機函數。
那么對於每個 i ,假設yi = H(xi)在{0, 1}l中均勻分布,並且每個yi的取值都相互獨立。則問題就轉變成:
在y1, ..., yq在{0, 1}l中均勻分布,則i ≠ j,yi = yj的概率是多大?
這個問題就是所謂的生日問題(Birthday Problem):假設有q個人在同一個房間內,那么他們中有兩個人的生日在同一天的概率為多大?(假設一年365天)
數學研究結果是:如果y1, ..., yq的取值是在{1, ..., N}之間均勻選擇的,那么當q=Θ(N1/2)的時候碰撞的概率大約為1/2。所以在生日問題中,如果房間里的人有23人,那么碰撞的概率就大於1/2。
從上面的問題可以看出:對於一個運行在時間T內的抗碰撞的哈希函數,假設每求一次H的值所花費的時間是一個單位時間,那么這個哈希函數的輸出長度至少為2logT比特,因為22logT/2=T。
這里需要注意幾個點:
- 哈希函數的輸出長度足夠長只是哈希函數安全的一個必要條件而非充分條件
- 生日攻擊只是用來尋找碰撞的
- 如果規定哈希函數的求H的次數必須小於2l,那么就不存在某種針對哈希函數的抗第二原像以及抗原像的攻擊方式
隨機預言機模型
隨機預言機模型假設存在一個公共的隨機的函數H,可以通過輸入 x 向 H 查詢 H(x) 的值。
為了區別,將前面所使用的模型(沒有隨機預言機的模型)稱為標准模型
在實際中並沒有隨機預言機的存在,不過有人建議使用公開的可信任的服務器來充當隨機預言機
隨機預言機模型提供了一種可用於設計和驗證密碼方案的形式化方法。
“預言機”:就是一個簡單的黑盒,將一個二進制串作為輸入,然后輸出另一個二進制串。而且這個黑盒的內部構造是未知且難以捉摸的。每一個通信方或者敵手都可以與這個黑盒進行交互(查詢)。
向預言機的查詢被認為是私密的。也就是說如果某人向預言機查詢 x 對應的輸出,那么沒有其他的人知道這個 x ,甚至沒人知道這個人向預言機進行了查詢。
預言機的一致性:如果預言機輸入 x 對應的輸出是 y ,那么無論查詢多少次,x 對應的輸出都應該是 y。
兩種構造隨機預言機的方法:
- 在一個從某個確定定義域到值域的所有函數集合中概率均勻的選擇一個函數
- 將預言機 H 想象成一張表,初始時這張表是空白的,當有人向預言機查詢 xi 對應的輸出時,預言機檢查(xi, yi)是否在這張表上:如果有,那么輸出yi;如果沒有,那么隨機生成一個長度為l(n)的二進制串 y ,然后將這個y作為輸出,並令yi=y,將(xi, yi)填寫到表格中。
隨機預言機模型的屬性:
- 如果H(x)沒有被查詢過,那么H(x)的值就是隨機的。也就是說隨機預言機 H 是一個真正的隨機數生成器,比PRG更強的隨機發生器。
- 當A向仿真器查詢 x 時,仿真器可以看到這個 x 以及H(x)。這個屬性並不與前面所提到的隨即預言機的私密性相矛盾,因為這個屬性只有在一個仿真器利用 A 作為自己的子程序的隨機預言機模型中才成立。
- 仿真器可以按照自己的選擇將 H(x) 作為 A 查詢 x 的結果。
隨機預言機的簡單說明:假設隨機預言機是一個將lin輸入映射lout輸出的函數,其中lin, lout > n,n是安全參數
- 利用隨機預言機構造偽隨機生成器:當lout > lin時,可以直接將RO作為一個偽隨機生成器
- 利用隨機預言機構造抗碰撞哈希函數:當lout < lin時,可以直接將RO作為一個抗碰撞的哈希函數
- 從隨機預言機構造偽隨機函數:當lin(n) = 2n, lout(n) = n 時,可以定義一個偽隨機函數:Fk(x) = H(k||x),其中|k| = |x| = n
哈希函數的運用
指紋與去重
但是用一個抗碰撞的哈希函數時,一個文件的哈希值可以充當該文件的標識符
換句話說 H(x) 對於文件 x 來說相當於x文件的指紋,所以可以通過檢查兩個文件的哈希值來確定兩個文件是否相等。
病毒指紋:病毒掃描器用於標識病毒然后阻止或者進行隔離。其中最重要的一個步驟就是將所有已知的病毒的哈希值存儲在一個服務器的數據庫中,然后每當下載一個應用或者接受一封郵件的時候就將其哈希值與數據庫中的哈希值進行比對。
去重復:在雲存儲中,多個用戶將他們的數據雲存儲服務器上,如果有多個用戶存儲了同樣的一個文件,那么雲存儲服務器只需要存儲一次就可以滿足需求。為了實現這個目標,每當用戶上傳文件的時候需要先上傳文件的哈希值,如果哈希值已經存儲在雲端,那么雲存儲服務器只需要簡單的添加一個在那個文件上的指針來表示該用戶也存儲了該文件。
P2P文件共享:在P2P文件共享系統中,服務器維持一個表來提供文件查找的功能,這些表中存儲着已有的文件的哈希值,而這些哈希值也相當於唯一的標識了每個文件,並且並不會占用太多的空間。
默克爾樹
假設某個用戶向服務器上傳多個文件x1, ..., xt,然后當用戶取回某個文件xi時,他希望這個文件是最初上傳的那個文件並且沒有被更改過。
在本地上為每個文件獨立的存儲一個哈希值h1, ..., ht,當取回文件xi的時候,只需要檢查H(xi)是否等於hi即可。然而這樣做的話,本地的存儲成本會隨着 t 的增大而線形增大。
另一種方法是計算h := H(x1, ..., xt),然后只在本地上存儲 h 的值。然而這樣做的話,用戶取回xi文件后想要檢查其完整性就需要取回所有的文件,這樣顯然會使效率變低。
現在最常用的方式是默克爾樹。
下面是一個默克爾樹的實例:

輸入的多個文件x1, ..., x8,分別計算出H(x1, x2), ..., H(x7, x8),然后將H(x1, x2)與H(x3, x4)進行異或得到h1...4,同樣的方法得到h5...8,然后再將h1...4與h5...8進行異或得到h1...8。
事實上,默克爾樹也提供了一種不同於前面所說的Merkel-Damgard轉換的哈希函數定義域的擴展方式,並且通常用MT表示由默克爾樹方式轉換而來的擴展定義域的哈希函數。
定理:如果哈希函數MT是由抗碰撞的固定長度哈希函數H經過默克爾樹擴展而來的,那么MT也是抗碰撞的。
回到最開始的那個問題,默克爾樹也提供了一種在O(log t)次交互內完成前面所說的問題的高效率解決方案。
用戶計算h := MT(x1, ..., x8),並且將 h 的值存儲到本地。
比如用戶需要取回文件x3,那么服務器需要將x4,H(x1, x2),h(5, ..., 8) 一並返回給用戶,然后用戶用MT的算法計算出h‘,然后將h’與存儲在本地上的h進行比對即可完成文件的完整性檢驗。
密鑰哈希
假設一個用戶在使用他的電腦前需要輸入一個密鑰,那么為了進行驗證,密鑰必須以某種形式存儲在電腦的某個位置。如果這個密鑰是直接以明文的形式存儲在電腦上,那么如果某個敵手在偷到了這台電腦后可以直接從硬盤上讀取這個密鑰,然后成功的作為用戶進行登錄。
可以存儲密鑰的哈希值代替密鑰的明文,從而避免這種風險。
在硬盤上存儲 hpw = H(pw),每當用戶輸入 pw‘ 進行登錄時,驗證H(pw')是否等於hpw。
但是如果密鑰是從一個比較小的空間里面選取的,那么敵手在從硬盤上讀取了hpw后可以一次遍歷每個可能的取值並進行驗證,知道驗證通過。
此外,哈希函數的抗原像的安全性質在這里也並不能提供充分的安全。因為哈希函數的抗原像性只能保證當 x 是在{0, 1}n這樣大的空間里面選取的時候,求H(x)的逆是困難的,但是並沒有保證當 x 是從別的某個空間或者按照某個分布選擇時,求H(x)的難度。而且,抗原像性並沒有對求H(x)的的時間進行具體的約束。
為了解決上面所說的安全威脅,可以使用"慢"的哈希函數或者使用多次迭代的哈希函數,從而增加敵手破解的時間。不過為了用戶體驗,需要將迭代次數設置的適當。
還有一種機制叫做"加鹽",當一個用戶進行登錄的時候,主機或生成一個長的用戶獨有的隨機數s(就是所謂的“鹽”),並且存儲(s, hpw=H(s,pw)),而不是像之前那樣簡單的存儲H(pw)。
因為s對敵手是未知的,所以破解這個密鑰對於敵手是十分低效率的。
密鑰推導
在有的時候,對於通信雙方來說,依靠共享的信息(如密碼或非均勻分布的生物特征數據)是很方便的。
通信雙方可以嘗試直接使用他們共享的信息作為密鑰,但通常這是不安全的。因為,例如,私鑰加密方案都采用統一分布的密鑰。此外,共享的數據甚至可能沒有用作密鑰的正確格式。
最小熵:如果對於每個固定值X PrX←x [X = x] ≤ 2-m,則概率分布X具有m位最小熵。也就是說,即使最可能的結果發生的概率也不超過2-m。
分布的最小熵用於度量敵手從分布中猜測抽樣值的概率。攻擊者的最佳策略是猜測最有可能的值,因此,如果分布具有最小熵m,則攻擊者的正確猜測概率最多為2-m。
最小熵的一個擴展叫做計算最小熵,它與最小熵的區別在於該分布只需要在計算上與具有給定最小熵的分布不可區分性。
密鑰推導函數提供了一種從任何高(計算)最小熵分布中獲得均勻分布字符串的方法:
考慮攻擊者對H(X)的不確定性,其中X是從最小熵m的分布中采樣的(作為一個技術點,我們要求分布與H無關)。攻擊者對H的每個查詢都可以看作是對X值的“猜測”;根據分布的最小熵假設,攻擊者對H進行q查詢,其查詢H(X)的概率最大為q·2-m。如果攻擊者沒有向H查詢X,那么H(X)是一個統一分布的字符串。
承諾方案
承諾方案允許通信方通過發送一個“承諾值” com 來承諾某個消息m。
承諾方案具有看似矛盾的性質:
- 隱藏性:com不能泄露m的信息。
- 綁定性:通信方如果已經對消息m做出了承諾com,那么com就不能是另一條不同的消息m'的承諾。
承諾方案事實上可以看作一個消息的數字信封:將消息密封在信封中並將其交給另一方可以保護隱私(直到打開信封)並具有約束力(因為信封是密封的)。
一個承諾方案的形式化定義如下:
- Gen:一個隨機化的算法,用於輸出一個公共參數params
- Com:一個以公共參數params和一條消息m∈{0, 1}n作為輸入,然后輸出一個承諾com。為了隨機化,選擇一個隨機數 r ,計算com := Com(params , m; r)
承諾過程:發送方隨機的選擇一個隨機數r,計算com := Com(params, m; r),然后將其發送給接收方。
兌現承諾過程:發送方隨后可以通過發送(m, r)給接收方來兌現承諾。接收方需要檢驗Com(params, m; r)是否等於com。
承諾方案的安全模型HidingA, Com(n):
- 運行Gen(1n)獲取公共參數params
- 敵手A將params作為輸入,然后輸出一對明文m0, m1
- 隨機選擇b∈{0, 1},然后計算com := Com(params, mb; r),並將com返回給敵手A
- 敵手輸出一個b’
- 如果b‘ = b,則這個實驗輸出1
BindingA, com(n):
- 運行Gen(1n)獲取公共參數params
- 敵手A將params作為輸入,然后輸出(com, m, r, m', r')
- 如果m ≠ m'並且Com(params, m; r) = com = Com(params, m'; r'),則這個實驗輸出1
定義:如果存在一個可忽略函數negl能夠滿足:Pr[HidingA,com(n)=1] ≤ 1/2 + negl(n) 並且Pr[BindingA,com(n)=1] ≤ 1/2 + negl(n),則稱這個承諾方案是安全的。
很容易從一個哈希函數 H 構造一個安全的承諾方案:需要對某條消息進行承諾時,隨機的選擇一個r∈{0, 1}n,然后輸出com := H(m||r),因為哈希函數是不需要密鑰的,所以Gen算法也不需要。
隱藏性:由哈希函數的抗原像性,敵手並不能從com中獲取關於m的信息。
綁定性:由於哈希函數的抗碰撞性。
