1.寫在前面
網路安全是一個非常重要的領域,今天和大家一起來學習和密碼相關的話題。
說到密碼大家肯定都不陌生,我們每個人都有一些列的密碼:郵箱密碼、社交網站密碼、各種app密碼等等,密碼就如同每個人網路領域的一把鑰匙。
對於我們使用者來說,我們儘量避免使用一些爛大街的密碼比如:123qaz、qazwsx等、提高密碼的複雜度、避免使用生日等基礎資訊作為密碼、另外要定期更換密碼等來確保個人資訊保安。
對於功能服務提供商來說,妥善儲存保管密碼是一個很重要的環節,一旦造成密碼洩漏等事故造成的影響會很大,要嚴肅對待。
然而並不是所有的事情都如我們所期盼那樣,網上使用者敏感資訊洩漏的事件比比皆是,國內外都有一些案例,可以自行搜尋引擎搞起。
前面鋪墊了一下於是引出本文的主題:
通過本文你將瞭解到以下內容:
- 密碼互動基本過程
- 明文儲存密碼
- 單向無鹽雜湊儲存
- 預計算雜湊鏈集合和彩虹表原理
- 雜湊+鹽儲存
- 專業密碼加密演算法
2.密碼互動過程
密碼一般是用在使用者登陸認證環節的,完整的過程包括:使用者輸入密碼、客戶端加密、網路傳輸、服務端驗證等。
從嚴格意義上來說每個環節都可能造成密碼的洩漏,過程越多出錯的概率就會越大,其中使用者登入認證的基本步驟可以歸納為:
- 使用者填寫賬號、密碼以及驗證碼等資訊;
- 在之前註冊時填寫的密碼經過加密儲存在資料庫中並做持久化備份,後續登入做驗證;
- 服務端從資料庫獲取該使用者名稱下的加密密碼,並且將使用者輸入使用相同加密過程計算結果,二者進行對比;
- 二者相同則使用者被授權登入,當然這裡不考慮手機驗證碼之類的措施;
- 如果二者不同則提示失敗,並且對最大嘗試次數做限制;
本文主要介紹服務端如何安全儲存密碼。
3.密碼的明文儲存
這種明文儲存密碼的方法讓人覺得不可思議,但是實際上真的有這樣做的,就像這樣把密碼明文直接儲存在MySQL,如圖:
這種明文儲存的密碼資料一旦被拖庫,就會造成很大的問題,對於一些私人小站可能會存在這種情況,因此我們日常一定要注意鑑別,以為這些站點的防範意識不夠,後臺漏洞很容易被不法分子利用造成洩漏,當然也有少數知名站點明文儲存的案例,只能說有點無語了。
現在的密碼太多了,很多人不借助一定規律或者工具的前提下會將1-2個常用密碼用在很多站點,這樣就出現了木桶效應:只要一個站點的密碼被洩漏其他站點的密碼也就不安全了。
大錘就是密碼太多他記不住,索性就用一個密碼註冊登陸日常的很多app,有一天他為了玩遊戲註冊了一個小破站點,後來密碼洩漏了,就這樣他的其他常用app就面臨著被盜的風險。
對於這種明文儲存的案例,網上有人說這是燈下黑...,讓我聽著有種網劇看多了的感覺,個人覺得主要原因就是意識不夠。
說起這個讓我想起,有次放假回老家,坐客運車途中要加油,正在加油的時候,車上有個人接打電話,沒過一會兒車上很多人不樂意了,開始嚷嚷他別打了,哈哈哈。
4. 單向無鹽雜湊儲存
在談論單向無鹽雜湊儲存之前,我們先來達到一個共識問題:我們常說的md5/sha其實都是摘要演算法,並不是加密演算法。
4.1 摘要演算法和加密演算法
加密演算法和摘要演算法之間有很大區別,雖然都是把明文進行變形處理,但是加密演算法必然對應解密演算法,也就是輸入值經過加密演算法處理之後可以使用解密演算法還原,但是摘要演算法一般認為是單向不可逆的,沒辦法輕易還原原始輸入。
如圖展示了加解密可逆過程(注加密密文是我胡亂寫的):
如圖展示了摘要演算法不可逆過程(注摘要密文是我胡亂寫的):
簡單瞭解了摘要演算法和加密演算法的區別與聯絡之後,我們可以知道摘要演算法是單向的,我只知道原始輸入A的摘要輸出是B,但是根據B很難推出來A。
就像你用一把鎖和一把鑰匙,但是你把鑰匙扔到了茫茫大海,在不損害箱子的前提下你只能一直暴力嘗試,在試了第20200404把鑰匙的時候發現開啟了,恍然大悟原來是這把鑰匙啊!
4.2 雜湊衝突和暴力破解
前面舉了用鑰匙開箱子的問題,最終還是被開啟了,先別高興,原生鑰匙也不一定就是這把呢,因為有雜湊衝突,巧了可以開啟,來看個圖先:
圖中我們用m輸入進行摘要運算之後得到了相同的摘要值,也就是開啟了箱子,但是真實的輸入卻是k。這裡就引出了一個新問題:雜湊衝突及其影響。
那麼雜湊衝突是什麼?又為啥會衝突呢?
我們知道摘要演算法生成的字串的長度一般是固定的有128位 256位之類的,摘要演算法的神奇之處在於可以把任意長度的資料轉化為固定長度的字串。
換句話說雜湊後的字串長度有限,那麼總的集合就有限,這是個組合的問題很容易想到,但是輸入是無限的呀!
極端一點你寫了個摘要演算法長度是4的十六進位制字串,也就只有65536個空間,實際上輸入樣本數量根本不需要65536就幾乎必然出現衝突,因為不出現衝突的要求是非常高的,不信你看看生日悖論問題就知道了。
生日悖論是指在不少於 23 個人中至少有兩人生日相同的概率大於 50%。例如在一個 30 人的小學班級中,存在兩人生日相同的概率為 70%。對於 60 人的大班,這種概率要大於 99%。從引起邏輯矛盾的角度來說,生日悖論並不是一種 “悖論”。但這個數學事實十分反直覺,故稱之為一個悖論。
生日悖論的數學理論被應用於設計密碼學攻擊方法——生日攻擊。
想想也是如此,無限輸入樣本對有限摘要集合,存在衝突幾乎是必然的:
還是暈菜的話,你想想北京13號線的早高峰,車廂10個人不擁擠,瞬間來了200個人,你說擠不擠,衝突不衝突,如圖:
4.3 單向無鹽雜湊儲存安全性
前面用了兩個小節闡述了摘要演算法和加密演算法的區別,以及雜湊衝突,觀點都很中立,在瞭解這些必備知識之後,我們不禁要問:單向無鹽雜湊儲存密碼安全嗎?
安全都是相對的,沒有絕對的安全,作為防守方只能讓攻擊方的時間和機器成本都高到無法接受,我們才是比較安全的。
換句話說攻擊方理論上可以破解我的密碼,但是需要300年又或者攻擊方要想在可接受的時間內破解那麼就得找個天文數量級的儲存空間,作為防守方你怕啥?
怕啊!萬一有時間和空間都能接受的方案咋整!先賣個關子,後面重點討論這種可怕的Trade-Off方案。
4.3.1 線上加解密實驗
常見的摘要演算法md5和sha1的長度分別為128位和160位,這裡的位是二進位制的,轉化為16進位制之後長度分別為32位和40位,從長度上來說sha1相比md5衝突更小一些,那麼我們就來試試sha1的破解速度吧!
筆者隨意找了一個線上sha1加解密的網站,這個網站的簡介看著還蠻厲害的,不過都是針對md5的,看來md5已經被如此嫌棄了:
本站專業針對md5等雜湊演算法進行線上解密,可上傳檔案線上批量破解,最多可支援數萬個密碼。單單MD5就擁有64T的密碼資料庫,3萬多億條,本站支援十幾種常見的雜湊演算法線上解密,查詢時間不到0.1秒。
摩拳擦掌趕緊試驗一把:
加密很快完成,接著解密一下:
啊呀很快就出結果了,之前我跑了幾個解密,現在讓我登入才展示,我懶得登入,不過確實是解密成功了,0.01秒所言非虛。
這怎麼可以,我再試個強密碼:
速度還是很快,生成了一個長度為40位的16進位制小寫字串,筆者還是很自信的,這麼老長破解去吧!啊哈哈....
果然,它跪了,所以增加密碼強度多麼重要!
4.3.2 國內外撞庫研究
我國著名的密碼學家山東大學的王小云教授先後對md5和sha1進行了深入研究並且在2004年左右取到了很大的進展,因此目前對於一些高安全要求的場合已經開始使用更高的摘要演算法比如:sha224、sha256、sha384、sha512等演算法,也就是長度更長了,集合空間更大撞庫可能更小,感受一下這個長度的變化(左右滑動):
明文輸入:1233445TGNVF
sha1密文:fc58f924f193654f6388cac13b6061e99c7dbabc
sha224密文:97cdfc11422a8311e812809711b3159c4530f5f183841f7fe111fd92
sha384密文:2de74b23f770126eb51ec328f591c2290fd3bed8756d0e4dffa32af9296006444c334288bd820b1297d8087977131f0f
sha512密文:96be48daec3779f6d83b991a4f281280884578b2b68a2cf5c481838e48c199c8795f89328a85f208d791465bad28acac440be3a1397eafb0bfefd60d6b9f8a9b
複製程式碼
GPU在進行密碼破解領域有絕對的把控力,對於md5和sha1這些演算法運算速度非常之快達到每秒x億次,如果再串聯多組GPU進行破譯那麼速度將更快。
但是就目前而言sha224/256/385/512演算法還是安全的,在2017年穀歌宣佈其對sha1撞庫的最新研究,基於此呼籲全球相關機構公司進行演算法升級,如圖展示了谷歌使用兩份不同的檔案得到相同sha1的例子:
儘管獲得了撞庫進展,但是谷歌付出的成本也是巨大的:攻擊演算法分為兩個階段,其中第一個階段如果使用單CPU需要6500年,第二階段使用單GPU需要110年,可謂成本之高。
所以我們如果採用單向無鹽雜湊儲存密碼時要避免使用MD5/sha-1這些被大量研究過的短摘要,可以使用sha-256這種更安全的摘要演算法,比特幣目前就有使用sha-256作為其相關演算法。
4.3.3 演算法升級的思考
更換更安全的摘要加密演算法確實是有一定效果的,但是算力的進步是我們無法預測的,正如md5和sha-1剛被應用時也是當前的算力無法逾越的,但是隨著平行計算和量子計算的發展,諸如sha-256/512這些現在看來更安全的演算法什麼時候會被證明又不安全也不得而知。
但是如果我偏偏想把單車開出摩托的感覺咋辦?怎樣用md5這種弱摘要演算法能不能實現一個強密碼儲存系統呢?
面對這個樸實的訴求,我們接著聊。
5.彩虹表攻擊
攻擊是為了更好的防守,相剋相生。
5.1 暴力破解
暴力破解一般可以分為 計算暴力破解和查表暴力破解,如圖:
在說彩虹表Rainbow Table之前先說一下字典窮舉,這個比較好理解:比如某家網站的密碼構成是長度為12位含字母、數字、特殊符號,這樣我們就可以根據這個特徵生成一個所有密碼的全排列集合,然後再進行比如md5摘要計算得到一個雜湊值,然後把這個對映關係儲存下來。
使用字典法暴力破解時就如同一個巨大的hash表可以迅速降低時間,但是隨著加密演算法的升級和密碼複雜度的提升,字典就會變得非常非常大,理論上無法儲存使用。
人們通過努力找了一種時間和空間折中的方案-彩虹表,它將單獨時間或者單獨空間的不可接受變為可接受,可以說是個非常有用的東西,第一次聽這個名字時詫異於為什麼叫彩虹表。
5.2 空間儲存效率問題
在探究彩虹表之前,我們先思考這樣一個問題:如何用最少的儲存空間表達最多的資訊量呢?
C語言中的例子
想一下在C語言中我們在傳結構體或者陣列時一般只傳遞首地址,其他的元素可以根據這個首地址和偏移量來實現訪問,所以我們用一個地址就可以遍歷所有的變數,儲存效率很高。二叉樹的例子
二叉樹的鏈式儲存和順序儲存都可以藉助於根節點,依據左右孩子節點的關係從而實現全樹的檢索和遍歷,這樣我們在對外傳輸二叉樹時只需要給出根節點即可,不必全部給定。西遊記的例子
西遊記主角是師徒5人,算了白龍馬的...,我們可以儲存這5個人的資訊,但是覺得有點浪費啊,因為我們知道他們5個之間是有關係的,儲存一個就能找到另外四個了。
如圖展示了全量儲存5人資訊的場景:
如圖展示了利用師徒之間的關係部分儲存:
簡單說明下:只儲存唐僧,唐僧為入口根據其大徒弟關係得到了孫悟空,從孫悟空依據師弟關係得到另外三人,這個很像資料庫的索引了...
畫外音:有時候很多元素內部是存在聯絡的,我們不必全量展示和儲存這些資訊,這樣會造成空間上的浪費,如果我們藉助於這些內在聯絡就可以用少量資料作為入口獲取到所有的資料,這是一種思想吧!
理解這個儲存效率問題對於認識彩虹表有很大的幫助。
試想字典窮舉是把建立所有明文和密文之間的對映,這樣就等價於唐僧師徒5人每個人都儲存,但是如果我們找到了某些明文之間的內在聯絡,那麼我是否可以只儲存少量明文就可以表達這些具備內在聯絡的明文和密文的對映關係呢?
答案是肯定的,但是彩虹表對於明文的內在聯絡是建立在數學的基礎上的,我們來繼續探討,彩虹表的H函式和R函式。
5.3 H函式和R函式
各位讀者坐穩扶好打起精神,H函式和R函式是理解彩虹表的關鍵所在。
先解釋一下H函式和R函式分別是什麼及其內在聯絡:
- H函式
H函式也就是雜湊函式,實現明文到密文的單向不可逆轉換; - R函式
R函式理解為Reduction function,這裡看到Reduction這個單詞,其實在之前P和NP問題的文章中,我們有接觸到這個單詞,歸約/約化的意思。 - H函式和R函式的關係
一句話概括:H函式的定義域是R函式的值域,H函式的值域是R函式的定義域,但是R並不是H的反函式,因為H函式是不可逆的。
舉個例子:
假如密碼範圍是長度在10個以內的字母數字組合,不區分大小寫,假設輸入明文為abcdfg,則存在如下關係:
// 雜湊函式H實現明文向密文的對映
H(abcdfg)=AB8GFTYG
// 歸約函式R實現密文向'明文相同格式字串'的對映
R(AB8GFTYG)=erfdtk複製程式碼
特別地,R函式將H函式的輸出作為輸入,也就是H的值域作為R的定義域,R函式生成了erfdtk,這個新明文字串並不是原始輸入的明文,但是格式卻是相同的。
這裡可以隱約感受到R函式的重要性,它可以將相同格式的明文生成的密文作為輸入,進而輸出相同格式的新明文,從而產生一個相同格式的明文的集合鏈條,也就是找到了一類有內在聯絡的明文。
換句話說,我們可以只儲存一個明文,中間把多個H-R進行組合串聯起來,從而形成一個明文-密文的對映集合,也就是空間減少了但是資訊量並沒有減少,這麼看來R函式確實很cool。
畫外音:這裡提到的R函式生成相同格式的新明文,"相同格式"這個詞語的理解不好拿捏,需要藉助數學手段來實現,我們暫且簡單理解為長度和組合方式類似吧!
5.4 預計算雜湊鏈和R衝突
在彩虹表出現之前有種預計算雜湊鏈集合,就是多組雜湊鏈組成的明文-密文集合,這裡簡單提一下。
前面提到了一組H-R函式,實際上只有多組H-R函式才有實際意義,比如有2000組H-R組合,那麼我們將有2000對明文-密文的對映,但是隻需要儲存非常小的一部分即可,這也就是我們要說的雜湊鏈,如圖為2組H-R函式組成的雜湊鏈:
上圖展示的兩組H-R函式中的R都是相同的,由於雜湊衝突的存在我們並不能表示全部獨立的明文,這樣空間儲存率就打折了,看下這個圖:
上面兩條雜湊鏈EDEDED和FEDECE經過R函式計算分別得到222和333,222經過H函式得到FEDEFE,再經過R函式也得到了333,這樣兩條雜湊鏈就存在重疊部分,也就是R函式出現了衝突。
// 雜湊鏈中的R函式衝突
R(FEDECE)=333R(FEDEFE)=333複製程式碼
更重要的是這種衝突是不好被發現的!
為啥這麼說呢,因為實際中只儲存雜湊鏈中的首尾兩個明文,對於上圖中中間發生了R衝突,但是從重合起點看上面的鏈比下面的鏈位置更靠後,下面的鏈會繼續進行餘下的H-R函式最終生成尾部明文是555,然而上面的鏈生成尾部明文是444,在實際儲存中是這樣的:
//雜湊鏈1
111->444
//雜湊鏈2
454->555複製程式碼
也就是假如你設計的k=2,也就是每組2個H-R函式,兩個雜湊鏈可以表示8個明文,但是實際上333和444是存在重複的,從而只有6個有效不同的明文,由於尾部明文的不同,這種重疊是無法被檢測的。
這麼看來擁有個分佈均勻的R函式是多麼重要,但是在幾千組H-R函式中要求完全沒有衝突是非常難的,於是出現了多組R函式的新形式。
畫外音:多組R函式的思路和布隆過濾器的多個hash函式的思想很相似
你可能會問為什麼不考慮H函式的衝突情況,因為H函式就是我們需要破解的加密函式,它本身的衝突概率非常低要不然就不用費這麼大勁搞彩虹表了。
5.5 彩虹表的基本原理
彩虹錶針對雜湊鏈集合的R函式衝突帶來的重疊引入了多組不同的R函式系列,從而一個鏈上每個位置的R函式都是不同的,如圖:
需要注意的是多個R函式的引入仍然不可避免衝突問題,但是這種情況下的衝突影響遠小於雜湊鏈集合中單個R函式的影響,如圖展示了一種常見的彩虹表R函式衝突(黑色加重部分):
具體來說就是在不同位置出現了衝突:
// 不同輸入 不同R函式產生相同的明文
R1(FEDECE)=333
R2(FEDEFE)=333複製程式碼
但是很快在下一個不同的R函式,R3和R2的作用下就不再重疊了,可覆蓋的明文數量影響不大,也只有333出現了重疊後續是獨立的。
極端情況下相同位置的衝突由於每個位置的R函式相同所以最終尾部明文仍然是相同的,可以進行糾正刪除從而減少冗餘資料,如圖:
綜上可知,彩虹表引入一組多個R函式確保了相同位置出現R衝突時的檢測刪除和不同位置出現R衝突的影響最小化,相比雜湊鏈集合有一些優勢。
讀到這裡我們對於為什麼叫做彩虹表隱約有了一點感覺,大概意思就是每一組雜湊鏈都有不同的R函式,就像這樣:
5.6 彩虹表的攻擊簡單過程
彩虹表涉及一個複雜的建表過程,並且不同格式長度的密碼和不同的雜湊函式都會有不同的彩虹表,網上有一些現成的彩虹表,感興趣的讀者可以根據自己的現狀下載一些彩虹表資料進行驗證,一般來說在實用的彩虹表在100GB以上。
要增加破解的概率就需要完善的彩虹表資料作為支撐,彩虹表的意義就在於把計算暴力破解和空間列舉結合起來。
來簡單說一下彩虹表的攻擊過程吧:
假設有K組H-R函式組合,彩虹表按照[head_string,tail_string]格式儲存,如圖:
- 假定給定的密文P位於最後一組H-R中,也就是第K個R函式存在R(P)=tail_string,如果tail_string存在於彩虹表中,則從head_string進行推算;
- 如果第K個R函式生成的結果不存在,則向前繼續搜尋第K-1個R函式一直計算到最後獲取tail_string,再判斷是否存在;
- 最壞的結果是從第1個R函式推算到最後得到的tail_srting仍然不存在,則說明這條鏈匹配失敗;
到這裡,我們基本上對彩虹表的原理和過程有了粗淺的認識,但是彩虹表也不是無敵的,因為它有個強大的對手【雜湊+鹽】儲存。
6. 雜湊+鹽組合加密儲存
一直在說無鹽單向雜湊儲存,但什麼是鹽呢?
簡單來說,鹽就是在使用者輸入密碼的基礎上增加的額外部分資料,這部分資料也參與計算雜湊儲存密碼。
H(user_input_string+slat)=new_password複製程式碼
和做菜一樣,在儲存密碼中加鹽也是技術活,不由得要問:為什麼加鹽就把單向雜湊變得這麼強大了呢?
其實很簡單,因為鹽不知道是怎麼加的,也不知道加的什麼!
如圖展示了一個使用彩虹表破解明文之後進行登陸仍然失敗的情況:
暴力破解得到的密碼是bnghuopyi99,輸入之後失敗,因為使用者輸入的明文是nghuopyi,鹽是b99,混合方式是取鹽的第一個字元放在使用者輸入最前面,剩下的追加在後面從而形成了bnghuopyi99。
隨機的鹽和不確定的新增方式,讓彩虹表不那麼給力了,換句話說每個使用者可能有單獨的混合方式,破解成本大大增加。
到這裡,彷彿雜湊+鹽的方式還是不錯的,但是這仍然不是最優的解決方案,我們繼續來看。
7. 專業的密碼加密演算法
前面我們學習的一些比如sha256這些演算法本質上並不是為了儲存密碼設計的,相反這些摘要演算法有其主要用途,那麼不禁要問:有沒有專門為密碼設計的加密演算法呢?
答案是肯定的。
我們都知道GPU的效能是十分恐怖的,計算速度非常快,試想我們把加密演算法變慢,攻擊方也必然會被拖慢,比如加密演算法需要200ms完成加密儲存,這個時間對於使用者而言是可以接受的,但是對於攻擊方來說時間成本就非常大了。
聽著有種故意把對手繞暈的感覺,本質上專業的密碼加密演算法可以設定強度,來提高暴力破解的時間成本,比較常見的加密演算法有以下幾種:
- bcrypt演算法
bcrypt 是專門為密碼儲存而設計的演算法,基於Blowfish 加密演算法變形而來,由 Niels Provos 和 David Mazières 發表於 1999 年的 USENIX。bcrypt 的work factor引數, 可用於調整計算強度,而且 work factor 是包括在輸出的摘要中的,隨著攻擊者計算能力的提高,使用者可以逐步增大 work factor
- PBKDF2演算法
PBKDF2(Password-Based Key Derivation Function) PBKDF2 就是將 salted hash 進行多次重複計算,這個次數是可以設定的,從而提高演算法的加密時間。
目前這兩個演算法都有Python和C/C++版本,感興趣的讀者可以試一下,從專業的角度來說,採用專業的密碼加密演算法是最好的解決方案了。
8.寫在最後
本文通過大家熟悉的密碼互動場景作為出發點闡述了明文儲存密碼、單向無鹽雜湊儲存、預計算雜湊鏈集合、彩虹表、雜湊+鹽儲存、專業密碼演算法儲存等幾個方面的相關知識。
其中,單向無鹽雜湊儲存、彩虹表、雜湊+鹽儲存內容相對較多,由於其中涉及了較多的數學背景知識,篇幅以及本人水平所限並未深入展開,從實用的角度來說開發者建議採用專業的密碼加密演算法,作為使用者也要增加密碼意識。
若評論不自由,則讚美無意義。
如有問題,請私信與我聯絡。
最後,依然感謝各位讀者的耐心閱讀,完結。
9.巨人的肩膀
- https://zhuanlan.zhihu.com/p/29167729
- https://www.pressc.cn/853.html
- https://www.cnblogs.com/milantgh/p/3612318.html
- http://www.91ri.org/7593.html
- https://www.jianshu.com/p/732d9d960411