回首近幾年,我有幸經歷了兩個相互衝突、卻又使人着迷的時代潮流變遷。第一個潮流變遷是:專家學者們耗費四十年設計的密碼學,終於派上用場;從信息加密、電話安全、到加密數字貨幣,咱們能夠在生活的方方面面發現使用密碼學的例子。算法
第二個潮流變遷是:全部密碼學家已經作好準備,迎接以上美好的幻滅。數組
正文開始以前我得重申一下,本文所講的不是所謂量子計算啓示錄(末日預言),也不是要講 21 世紀密碼學的成功。咱們要談論的是另外一件未成定局的事情——密碼學有史以來最簡單的(也是最酷炫的)技術之一:基於散列函數的簽名。安全
在 20 世紀 70 年代末,Leslie Lamport 發明了基於哈希函數(Hash Function,又稱散列函數)的簽名 ,並通過 Ralph Merkle 等人進一步改進。然後的不少年,這被視爲密碼學領域一灘有趣的「死水」,由於除了相應地產生冗長的(對比其餘複雜方案)簽名,基於哈希函數的簽名好像沒有什麼做用。然而近幾年來,這項技術彷佛有了復甦的跡象。這很大程度歸因於它的特性——不一樣於其餘基於RSA或離散對數假設的簽名,哈希函數簽名被視爲能夠抵抗量子計算攻擊(如 Shor’s 算法)。函數
首先,咱們進行一些背景介紹。工具
在正式介紹哈希函數簽名以前,首先你得知道密碼學中的哈希函數是什麼。哈希函數能夠接受一串字符(任意長度)做爲輸入,通過「消化」後,產生固定長度的輸出。常見的密碼學哈希運算,像是 SHA二、SHA3 或 Blake2 等,經運算會產生長度介於 256 ~ 512 位的輸出。優化
一個函數 H(.) 要被稱做「密碼學」哈希函數,必須知足一些安全性的要求。這些要求有不少,不過咱們主要聚焦在如下三個方面:加密
咱們相信全部本文提到的哈希函數示例都能提供上述的全部特性。換言之,沒有任何可行的(甚至是概念上的)方法能破解它。固然這種狀況也是會變的,若是破解的方法被找到,咱們固然會當即停用哈希函數(稍後會討論關於量子計算攻擊的特例)。翻譯
咱們的目標是使用哈希函數構造數字簽名方案,所以簡要回顧數字簽名這個詞能帶來很大的幫助。設計
數字簽名方法源於公鑰的使用,使用者(簽署人)生成一對密鑰:公鑰和私鑰。使用者自行保管私鑰,並可以用私鑰「簽署」任何消息,從而產生相應的數字簽名。任何一個持有公鑰的人都能驗證該消息正確性和相關簽名。cdn
從安全的角度來講,咱們但願簽名是不可僞造的,或是說「存在不可僞造性」。這意味着攻擊者(沒有私鑰控制權的人)沒法在某段消息上僞造你的簽名。有關數字簽名安全的更多定義請參閱這裏。
在 1979 年,一位名叫 Leslie Lamport 的數學家發明了世界上第一個基於哈希函數的簽名。Lamport 發現只要使用簡單的哈希函數,或是單向函數,就能夠構建出很是強大的數字簽名方法。
強大的前提是,用戶只須要作一次簽名的動做就能保證安全性!後續會作更詳細的闡述。
爲了更好的討論,咱們假設如下條件:一個哈希函數,它能接受 256 位的輸入併產生 256 位的輸出; SHA256 哈希函數就是個絕佳的示範工具;咱們也須要能產生隨機輸入的方法。
假設咱們的目標是對 256 位的消息進行簽名。要獲得咱們須要的密鑰,首先須要生成隨機的 512 個位字符串,每一個位字符串長度爲 256 位。爲了便於理解,咱們將這些字串列爲兩個獨立的表,並以符號代指:
sk0= sk10, sk20, …,sk2560
sk1= sk11, sk21, …,sk2561
咱們以列表 (sk~0~, sk~1~) 表示用來簽名的
pk0= H(sk10), H(sk20), …,H(sk2560)
pk1= H(sk11), H(sk21), …,H(sk2561)
如今咱們能夠將公鑰 (pk~0~,pk~1~) 公佈給全部人知道。好比說,咱們能夠把公鑰發給朋友,嵌入證書中,或是發佈在 Keybase 上。
接着咱們使用密鑰對 256 位消息 M 進行簽名。首先咱們得將消息 M 重現爲獨立的 256 位元(Bit,又稱「比特」):
M1, M2, …, M256 ∈ {0, 1}
簽名算法的其他部分很是簡單。咱們從消息 M 的第 1 位至第 256 位,逐一相應在密鑰列表中的其中一個密鑰上取出字符串。而所選密鑰取決於咱們要簽名的消息每一位(bit)的值。
具體一點地說,對於 i = [1,256],若是第 i 位的消息位元 Mi = 0,咱們會從 sk0 表中選擇第 i 個字符 (ski0) ,做爲咱們簽名的一部分;若是第 i 位的消息位元 Mi = 1,咱們則從 sk1 表進行前述過程(即,若是咱們要對消息 M 中的第 3 位進行簽名,而該位值爲 0,則使用 sk0 中的第三位,sk03,做爲咱們簽名的一部分)。對每一個消息位元完成此操做後,咱們將選中的字符串鏈接,獲得簽名。
過程如圖示說明,由於部分過程化簡,密鑰和消息長度只有 8 個 bit(位元)。要注意的是,每一個色塊表明的都是不一樣的隨機 256 位字符串。
當某個用戶(已經知道公鑰 (pk0, pk1))收到消息 M 和簽名,她可以輕易地驗證這個簽名。咱們以 si 表示簽名中第 i 個組成部分,用戶可以檢查相應的消息 Mi 並計算哈希值 H(si) 。若是 Mi = 0 ,則哈希值必須匹配公鑰 pk0 中的元素;若是 Mi = 1 ,則哈希值必須匹配公鑰 pk1 中的元素。
若是簽名中的每一個元素通過哈希運算後,都能找到對應的正確部分的公鑰,咱們就會說這個簽名是有效的。如下是驗證過程圖示,簽名中至少有一個簽名元素:
若是你開始以爲 Lamport 的計劃有些瘋狂,你既是對的,也是錯的。
首先探討下這個數字簽名方法的弊端。咱們會發現, Lamport 方法的簽名和密鑰實在太大了,大約有數千 bits。並且更要命的是,這個方法存在嚴重的安全侷限:每一個密鑰只能被用來簽名一個消息,因此 Lamport 方法做爲「一次性簽名」 在這裏被拿來舉例。
這種安全侷限爲何存在呢?回想一下, Lamport 簽名代表了在各個消息位元上可能的兩個密鑰之一。假如只須要簽署一條信息,這個簽名方法徹底沒問題。然而,若是我簽署了兩條在每個對應位置 i 的 bit 值都不一樣的消息,而後連同密鑰一塊兒發送出去,這可能致使大問題!
假設攻擊者從不一樣的消息獲得兩個有效的簽名,她便可以發起 「混合搭配(mix and match)」攻擊,成功僞造簽署第三條我從未簽名過的信息。如下圖示說明這個攻擊過程:
這個問題的嚴重程度取決於你簽名的消息的相異程度,以及有多少消息被攻擊者給截獲了。但總的來講,這確定不是件好事。
讓咱們總結一下 Lamport 簽名方法;它很簡單、快速,但它在實際應用上還有不少不足之處。或許咱們能夠作一點優化?
Lamport 簽名方法是個好的開端,可是沒法用單一密鑰簽名多條信息,是它最大的弊端。Martin Hellman 的學生 Ralph Merkle 由此獲得大量啓發,他很快地想到了一個聰明的解決辦法。
雖然咱們不打算在這裏展開解釋默克爾方法的步驟,咱們仍是來試着理清 Ralph 的想法。
咱們如今的目標是用 Lamport 簽名方法簽署 N 條信息。最直觀的方法是,以最初的 Lamport 方法生成 N 個不一樣的密鑰對,而後將全部公鑰關聯起來,集合成一個超巨大的 mega-key。(mega-key是我現編的術語。)
若是簽名者繼續拿着這麼一把密鑰集合,她就能夠對 N 條不一樣消息進行簽名,嚴格上來說這也只是一把 Lamport 密鑰。看起來,這樣就解決了密鑰重用的問題。驗證者也有對應的公鑰可以驗證全部收到的消息。沒有任何的 Lamport 密鑰被使用兩次。
很明顯的,這種方法很糟糕,由於時間成本過高了。
具體地說,上述這種天真的方法中,爲了達到要求的簽名次數,簽名者必須分發比普通 Lamport 公鑰還要大數倍的公鑰(簽名者還要繼續拿着一樣巨大的私鑰)。人們極可能會對這種結果感到不滿,也會反思有沒有辦法避免這種負做用產生。接下來,讓咱們進入 Merkle 方法。
Merkle 方法但願能找到一個能簽署多條不一樣消息的方法,同時避免公鑰的成本線性激增。Merkle 方法的實現以下:
關於 Merkle tree 的更多描述請點擊這裏。概略地說,Merkle 方法提供了一種能收集不一樣的值,並用一個 「根」 哈希(例子中使用的哈希函數,長度爲 256 bits)表明所收集的值的方法。給出這個根哈希,就能簡單「證實」 某個元素存在於這個給出的哈希樹。並且這個證實的大小和葉節點數量成對數關係。
–
要簽名的時候,簽名者從 Merkle tree 中直接選擇公鑰,並用對應的 Lamport 密鑰簽名。接着她將獲得的簽名結果鏈接 Lamport 公鑰並附上「Merkle 證實」。Merkle root 能夠來佐證該默克爾樹中包含選中的公鑰(即整個方法使用的公鑰)。最後簽名者將整個集合看成消息簽名發送出去。
(驗證者只要直接將這個「簽名」分別解壓爲 Lamport 簽名、 Lamport 公鑰、 Merkle 證實,就能進行驗證。驗證者可以依靠拿到的 Lamport 公鑰驗證 Lamport 簽名,並用 Merkle 證實這把公鑰的確存在於 Merkle tree 中。只要知足這三個條件,驗證者就能確信簽名是有效的。)
這個方法的缺點是會將「簽名」大小增長兩倍以上。不過,如今 Merkle 方法主要的公鑰只是一串簡單的哈希值,使得這個方法比上面提到的原始 Lamport 方法更爲簡潔。
最後還有個優化部分,密碼學強度的僞隨機數發生器可以輸出生成各式各樣的密鑰,同時「壓縮」密鑰數據自己。這使得原先龐大的位元(顯然是隨機的)可以轉換爲簡短的「種子(seed)」。
很贊啦!
Merkle 方法使得一次性簽名轉變爲 N 次性簽名。構造這種方法仍然須要基於某些一次性簽名方法,好比 Lamport 方法;但不幸的是,Lamport 方法的(帶寬)成本仍相對高昂。
有兩種主要的方法能夠下降這些成本。第一種也是 Merkle 提出的;爲了更好的解釋許多強大的簽名方法,咱們優先說明這項技術。
回想一下 Lamport 方法,要對一條 256 位的消息進行簽名,咱們須要一個包含 512 個獨立密鑰(和公鑰)位串的向量,簽名自己就是 256 個密鑰位串的集合。(這些數字會被須要簽名的消息位元激活,位元能夠是 「0」 或 「1」 ,所以須要從兩張不一樣的密鑰表中提取適合的密鑰元素。 )
這裏引起了新的思考:若是咱們不對全部的消息位元進行簽名,會怎麼樣呢?
更詳細點說,在 Lamport 方法中,咱們經過輸出密鑰位串對一條消息的每一個位元進行簽名——不管它的值是什麼。若是咱們不要同時簽名一條消息中 0 和 1 的位元,而是隻簽名 1 的位元,那又會如何呢?這麼作可以將公鑰和私鑰的大小減半,由於咱們能夠徹底丟掉整條 sk0 列。
如今咱們只有單一列位串的密鑰 sk1,…,sk256,對消息的每一個位元 Mi = 1咱們都會輸出一個字符串 ski;對於消息的每一個位元 Mi = 0咱們都會輸出……無(由於許多消息都會包含不少的 0 位元,這麼作能縮減簽名大小,這些 0 位元將再也不帶來任何成本)。
這種方法的明顯缺陷是:它
舉例來講,假設有個攻擊者觀察到一條已經被簽名的消息,消息開頭是「1111…」。如今攻擊者想要在不破壞簽名的狀況下,將消息編輯成「0000…」,只須要刪掉這條簽名中的幾個組成部分便可!簡言之,雖然要將 0 位元「翻轉」 成 1 位元很困難,但反之要將 1 換成 0 就很是簡單了。
如今有了個解決辦法,並且它很是巧妙。
讓咱們接着瞧瞧。雖然沒法避免攻擊者將消息中的 1 改爲 0 ,但咱們能發現這些改動。只要將一個簡單的「校驗和(checksum)」附加到消息上,而後將消息和校驗和一塊兒簽名。對於簽名驗證者來講,她必須驗證整份簽名的兩個值,也須要肯定收到的校驗和是正確的。
咱們使用的校驗和很是小:它由簡單的二進制整數組成,表示原始消息中的全部 0 位元數。
若是攻擊者試圖修改消息內容(或是校驗和),使得部分 1 位元變成 0 位元,並無手段能夠阻止她。可是這種攻擊會增長消息中的 0 位元數,這會使得校驗和無效,驗證者從而會拒絕這個簽名。
固然,機智的攻擊者可能還會試圖混淆校驗和(校驗和也和消息一塊兒被簽名),增長校驗和的整數值來匹配她篡改的位元數。然而最關鍵的是,由於校驗和是二進制整數,若是要增長校驗和的值,攻擊者勢必得將一些 0 位元轉換成 1 位元。又由於校驗和也被簽過名,這種簽名方法從源頭阻止這種轉換(將 0 換成 1),所以攻擊者沒法得逞。
(若是你繼續記錄下去,的確會增長被簽名的「消息」的大小。在咱們的例子中,一條 256 位的消息的校驗和,須要額外的 8 位元及增長相應的簽名成本。不過,若是這條消息包含許多 0 位元,這麼作對於縮減簽名大小仍然很是有效。)
原文連接: https://blog.cryptographyengineering.com/2018/04/07/hash-based-signatures-an-illustrated-primer/
做者: Matthew Green
翻譯&校對: Ian Liu & 阿劍
以太中文網經受權轉載