天下武功,惟快不破。但密碼加密不一樣。算法越快,越容易破。前端
密碼破解,就是把加密後的密碼還原成明文密碼。彷佛有很多方法,但最終都得走一條路:暴力窮舉。也許你會說還能夠查表,瞬間就出結果。雖然查表不用窮舉,但表的製造過程仍然須要。查表只是將窮舉提早了而已。算法
密碼加密,用的都是單向散列計算。 既然單向,那就是不可逆,那隻能窮舉。窮舉的原理很簡單。只要知道密文是用什麼算法加密的,咱們也用相同的算法,把經常使用的詞組跑一遍。如有結果和密文同樣,那就猜中了。數據庫
窮舉的速度有多快?這和加密算法有關。加密一次有多快,猜一次也這麼快。例如 MD5 加密是很是快的。加密一次耗費 1 微秒,那破解時隨便猜一個詞組,也只需 1 微秒(假設機器性能同樣,詞組長度也差很少)。攻擊者一秒鐘就能夠猜 100 萬個,並且這還只是單線程的速度。後端
因此,加密算法越快,破解起來就越容易。數組
(題圖來自: netdna-ssl.com)瀏覽器
若是能提升加密時間,顯然也能增長破解時間。若是加密一次提升到 10 毫秒,那麼攻擊者每秒只能猜 100 個,破解速度就慢了一萬倍。怎樣才能讓加密變慢?最簡單的,就是對加密後的結果再加密,重複屢次。安全
例如,本來 1 微秒的加密,重複一萬次,就慢一萬倍了:性能優化
for i = 0 ~ 10000 x = md5(x) end
加密時多花一點時間,就能夠換取攻擊者大量的破解時間。服務器
事實上,這樣的「慢加密」算法早已存在,例如 bcrypt、PBKDF2 等等。它們都有一個難度係數因子,能夠控制加密時間,想多慢就多慢。多線程
加密越慢,破解時間越長。
最須要慢加密的場合,就是網站數據庫裏的密碼。
近幾年,常常能聽到網站被「拖庫」的新聞。用戶資料都是明文存儲,泄露了也沒法挽回。惟獨密碼,還能夠和攻擊者對抗一下。然而很多網站,使用的都是快速加密算法,所以輕易就能破解出一堆弱口令帳號。固然,有時只想破解某個特定人物的帳號。只要不是特別複雜的詞彙,跑上幾天,極可能就破出來。
但網站用了慢加密,結果可能就不同了。若是把加密時間提升 100 倍,破解時間就得長達數月,變得難以接受。即便數據泄露,也能保障「密碼」這最後一道隱私。
不過,慢加密也有明顯的缺點:消耗大量計算資源。使用慢加密的網站,若是同時來了多個用戶,服務器 CPU 可能就不夠用了。要是遇到惡意用戶,發起大量的登陸請求,甚至形成資源被耗盡。性能和安全老是難以兼得。因此,通常也不會使用過高的強度。
一些大型網站,甚至爲此投入集羣,用來處理大量的加密計算。但這須要很多的成本。
有沒有什麼方法,可讓咱們使用算力強勁、同時又免費的計算資源?
在過去,我的電腦和服務器的速度,仍是有較大差距的。但現在,隨着硬件發展進入瓶頸,這個差距正縮小。在單線任務處理上,甚至不相上下。客戶端擁有強大的算力,能不能分擔一些服務器的工做?尤爲像「慢加密」這種算法開源、但計算沉重的任務,爲什麼不交給客戶端來完成?
過去,提交的是明文密碼;如今,提交的則是明文密碼的「慢加密結果」。不管是註冊,仍是登陸。而服務端,無需任何改動。將收到的「慢加密結果」,當作原來的明文密碼 就行。之前是怎麼保存的,如今仍是怎麼保存。這樣就算被拖庫,攻擊者破解出來的也只是「慢加密結果」,還需再破解一次,才能還原出「明文密碼」。
事實上,「慢加密結果」這個中間值,是不可能破解出來的!由於它是一個散列值 —— 毫無規律的隨機串,例如 32 位十六進制字符串,而字典都是有意義的詞組,幾乎不可能跑到它!除非字節逐個窮舉。但這有 16^32 種組合,是個天文數字。因此「慢加密結果」是沒法經過數據庫裏泄露的密文「逆推」出來的。
或許你在想,即便不知道明文密碼,也能夠直接用「慢加密結果」來登陸。事實上後端儲存時再次加密,就沒法逆推出這個散列值了。
固然,不能逆推,但能夠順推。把字典裏的詞組,用先後端的算法依次執行一次:
back_fast_hash( front_slow_hash(password) )
而後對比密文,便可判斷有沒有猜中。這樣就能夠用跑字典來破解。可是有 front_slow_hash 這個障礙,破解速度就大幅下降了。
不過,前端的一切都是公開的。因此 front_slow_hash 的算法你們都知道。攻擊者能夠用這套算法,把經常使用詞組的「慢加密結果」提早算出來,製做成一個「新字典」。未來拖庫後,就能夠直接跑這個新字典了。
對抗這種方法,還得用經典的手段:加鹽。最簡單的,將用戶名做爲鹽值:
front_slow_hash(password + username)
這樣,即便相同的密碼,對於不一樣的用戶,「慢加密結果」也不同了。也許你會說,這個鹽值不合理,由於用戶名是公開的。攻擊者能夠對某個重要人物的帳號,單獨爲他創建一個字典。
那麼,是否能夠提供一個隱蔽的鹽值?答案是:不能夠。
由於這是在前端。用戶還沒登陸,那返回誰的鹽值?登陸前就能得到帳號的鹽值,這不仍是公開的嗎。因此,前端加密的鹽值沒法隱藏,只能公開。固然,即便公開,單獨提供一個鹽值參數,也比用戶名要好。由於用戶名永遠不變,而獨立的鹽值能夠按期更換。
鹽值能夠由前端生成。例如註冊時:
# 前端生成鹽值 salt = rand() password = front_slow_hash(password + salt) # 提交時帶上鹽值 submit(..., password, salt)
後端將用戶的鹽值也儲存起來。登陸時,輸完用戶名,就能夠開始查詢用戶對應的鹽值:
固然要注意的是,這個接口能夠測試用戶是否存在,因此得有必定的控制。
鹽值的更換,也很是簡單,甚至能夠自動完成:
前端在加密當前密碼時,同時開啓一個新線程,計算新鹽值和新密碼。提交時,將它們全都帶上。若是「當前密碼」驗證成功,則用「新密碼」和「新鹽值」覆蓋舊的。這樣更換鹽值,仍是隻用到前端的算力。
這一切都是自動的,至關於 在用戶無感知的狀況下,按期幫他更換密碼!
密文變了,針對「特定鹽值」製做的字典,也就失效了。攻擊者得從新制做一次。
密碼學上的問題到此結束,下面討論實現上的問題。
現實中,用戶的算力是不均衡的。有人用的是神級配置,也有的是古董機。這樣,加密強度就很難設定。若是古董機用戶登陸會卡上幾十秒,那確定是不行的。對於這種狀況,只有如下選擇:
根據大衆的配置,制定一個適中的強度,絕大多數用戶均可接受。但若是超過規定時間還沒完成,就把算到一半的 Hash 和步數提交上來,剩餘部分讓服務器來完成。
[前端] 完成 70% ----> [後端] 計算 30%
不過,這須要「可序列化」的算法,才能在服務端還原進度。若是計算中會有大量的臨時內存,這種方案就不可行了。相比過去 100% 後端慢加密,這種少許用戶「先後參半」的方式,能夠節省很多服務器資源。
對於請求協助的用戶,也必須有必定的限制,防止惡意透支服務器資源。
若是後端不提供任何協助,那隻能根據自身條件作取捨了。配置差的用戶,就少加密一點。用戶註冊時,加密算法不限步數放開跑,看看特定時間裏能算到多少步:
# [註冊階段] 算力評估(線程 1 秒後停止) while x = hash(x) step = step + 1 end
這個步數,就是加密強度,會保存到他的帳號信息裏。和鹽值同樣,強度也是公開的。由於在登陸時,前端加密須要知道這個強度值。
# [登陸階段] 先得到 step for i = 0 ~ step x = hash(x) end
這個方案,可讓高配置的用戶享受更高的安全性;低配置的用戶,也不會影響基本使用。(用上好電腦還能提高安全性,頗有優越感吧~)
但這有個重要的前提:註冊和登陸,必須在性能相近的設備上。若是是在高配置電腦上註冊的帳號,某天去古董機登陸,那就悲劇了,可能半天都算不出來。。。
上述狀況,現實中是廣泛存在的。好比 PC 端註冊的帳號,在移動端登陸,算力可能就不夠用。若是沒有後端協助,那隻能等。要是常常在低端設備上登陸,那每次都得乾等嗎?等一兩次就算了,若是每次都等,不如從新估量下本身的能力吧。把加密強度動態調低,更好的適應當前環境。未來若是不用低端設備了,再自動的調整回來。讓加密強度,能動態適應經常使用的設備的算力。
實現原理,和上一節的自動更換鹽值相似。
下面 YY 一個腦洞大開的方案,前提是網站有足夠大的訪問量。若是當前有不少在線用戶,它們不就是一堆免費的計算節點嗎?計算量大的問題,扔給他們來解決。
不過這樣作也有一些疑慮,萬一正好推送給了壞人怎麼辦?顯然,不能把太多的敏感數據放出去。節點只管計算,徹底不用知道、也不能知道這個任務的最終目的。
可是,若是遇到惡做劇節點,故意把數據算錯怎麼辦?因此不能只推送給一個節點。多選幾個,最終結果一致纔算正確。這樣風險機率就下降了。
相比 P2P 計算,網站是有中心、實名的,管理起來會容易一些。對於惡做劇用戶,能夠進行懲罰;參與過幫助的用戶,也給予必定獎勵。
想象就到此,繼續討論實際的。
或許你會問,「慢加密」不就是但願計算更慢嗎,爲何還要去優化?
假如這是一個自創的隱蔽式算法,而且混淆到外人根本沒法讀懂,那不優化也沒事。甚至能夠在裏面放一些空循環,故意消耗時間。但事實上,咱們選擇的確定是「密碼學家推薦」的公開算法。它們每個操做,都是有數學上的意義的。本來一個操做只需一條 CPU 指令,由於不夠優化,用了兩條指令,那麼額外的時間就是內耗。致使加密用時更久,強度卻未提高。
若是是本地程序,根本不用考慮這個問題,交給編譯器就行。但在 Web 環境裏,咱們只能用瀏覽器計算!相比本地程序,腳本要慢的多,所以內耗會很大。
腳本爲何慢?主要仍是這幾點:
腳本,是用來處理簡單邏輯的,並非用來密集計算的,因此不必強類型。不過現在有了一個黑科技:asm.js。它能經過語法糖,爲 JS 提供真正的強類型。這樣計算速度就大幅提高了,能夠接近本地程序的性能!
可是不支持 asm.js 的瀏覽器怎麼辦?例如,國內還有大量的 IE 用戶,他們的算力是很是低的。好在還有個後補方案 —— Flash,它有各類高性能語言的特徵。類型,天然不在話下。
相比 asm.js,Flash 仍是要慢一些,但比 IE 仍是快多了。
解釋型語言,不只須要語法分析,更是失去了「編譯時深度優化」帶來的性能提高。好在 Mozilla 提供了一個能夠從 C/C++ 編譯成 asm.js 的工具:emscripten。有了它,就不用裸寫了。並且編譯時通過 LLVM 的優化,生成的代碼質量會更高。
事實上,這個概念在 Flash 裏早有了。曾經有個叫 Alchemy 的工具,能把 C/C++ 交叉編譯成 Flash 虛擬機指令,速度比 ActionScript 快很多。
Alchemy 如今更名 FlasCC,還有開源版的 crossbridge
一些本地語言看似很簡單的操做,在沙箱裏就未必如此。例如數組操做:
vector[k] = v
虛擬機首先得檢查索引是否越界,不然會有嚴重的問題。若是「前端慢加密」算法涉及到大量內存隨機訪問,那就會有不少無心義的內耗,所以得慎重考慮。不過有些特殊場合,腳本速度甚至能超過本地程序!例如開頭提到的 MD5 大量反覆計算。
這其實不難解釋:
首先,MD5 算法很簡單。沒有查表這樣的內存操做,使用的都是局部變量。局部變量的位置都是固定的,避免了越界檢查開銷。其次,emscripten 的優化能力,並不比本地編譯器差。最後,本地程序編譯以後,機器指令就不會再變了;而現在腳本引擎,都有 JIT 這個利器。它會根據運行時的狀況,實時生成更優化的機器指令。
因此選擇加密算法時,還得兼顧實際運行環境,揚長避短,發揮出最大功效。
衆所周知,跑密碼使用 GPU 能夠快不少倍。GPU 能夠想象成一個有幾百上千核的處理器,但只能執行一些簡單的指令。雖然單核速度不及 CPU,但能夠經過數量取勝。暴力窮舉時,能夠從字典裏取出上千個詞彙同時跑,破解效率就提升了。
那可否在算法裏添加一些特徵,正好命中 GPU 的軟肋呢?
你們聽過說「萊特幣」吧。不一樣於比特幣,萊特幣挖礦使用了 scrypt 算法。這種算法對內存依賴很是大,須要頻繁讀寫一個表。GPU 雖然每一個線程都能獨立計算,但顯存只有一個,你們共享使用。這意味着,同時只有一個線程能操做顯存,其餘有須要的只能等待了。這樣,就極大遏制了併發的優點。
山寨幣遍地開花的時候,還出現了一個叫 X11Coin 的幣,據稱能對抗 ASIC。它的原理很簡單,裏面摻雜了 11 種不一樣的加密算法。這樣,製造出相應的 ASIC 複雜度大幅增長了。儘管這不是一個長久的對抗方案,但思路仍是能夠借鑑的。若是一件事過於複雜,不少攻擊者就望而生畏了,不如去作更容易到手的事。
之因此 GPU 能大行其道,是由於目前的加密算法,都是簡單的公式運算。這對 CPU 並沒太大的優點。可否設計一個算法,充分依賴 CPU 的優點?CPU 有不少隱藏的強項,例如流水線。若是算法中有大量的條件分支,也許 GPU 就不擅長了。
固然,這裏只是設想。本身創造加密算法,是很是困難的,也不推薦這麼作。
除了能下降密碼破解速度,前端慢加密還有一些其餘意義:
用戶輸入的明文密碼,在前端內存裏就已加密。離開瀏覽器,泄露風險就已結束。即便通訊被竊聽,或是服務器上的惡意中間件,都沒法拿到明文密碼。除非網頁自己有惡意代碼,或是用戶系統存在惡意軟件。
儘管大部分網站都聲稱,不會存儲用戶的明文密碼。但這並無證據,也許私下裏仍在悄悄儲存。若是在前端加密,網站就沒法拿到用戶的明文密碼了。也許正是這一點,不少網站不肯意使用前端加密。
事實上,其實網站不肯意也不要緊,咱們能夠本身作一個單機版的慢加密插件。當選中網頁密碼框時,彈出咱們插件。在插件裏輸入密碼後,開始慢加密計算。最後將結果填入頁面密碼框裏。這樣,全部的網站均可以使用了。固然,已註冊的帳號是不行的,得手動調整一下。
「前端慢加密」須要消耗用戶的計算力,這個缺點有時也是件好事。
對於正經常使用戶來講,登陸時多等一秒影響並不大。但對於頻繁登陸的用戶來講,這就是一個障礙了。誰會頻繁登陸?也許就是撞庫攻擊者。他們沒法拖下這個網站的數據庫,因而就用在線登陸的方式,不斷的測試弱口令帳號。若是經過 IP 來控制頻率,攻擊者能夠找大量的代理 —— 網速有多快,就能試多快。
但使用了前端慢加密,攻擊者每試一個密碼,就得消耗大量的計算,因而將瓶頸卡在硬件上 —— 能算多快,才能試多快。因此,這裏有點相似 PoW(Proof-of-Work,工做量證實)的意義。關於 PoW,之後咱們會詳細介紹。
儘管「前端慢加密」有很多優點,但也不是萬能的。上一節也提到,能減小風險,而不是消除風險。若是本地環境有問題,那任何密碼輸入都有風險。
下面咱們來思考一個場景:某網站使用了「前端慢加密」,但沒有使用 HTTPS —— 這會致使鏈路被竊聽。回顧 0×05 小節,若是拿到「慢加密結果」,就能夠直接登上帳號,即便不知道明文密碼。的確如此。但請仔細想想,這不也下降損失了嗎?
原本不只帳號被盜用,並且明文密碼也會泄露;而現在,只是帳號被盜用,明文密碼對方仍沒法得到。因此,前端慢加密的真正保護的是「密碼」而不是「帳號」。帳號被盜,密碼拿不到!
若是攻擊者不只能竊聽,還能控制流量的話,就能夠往頁面注入攻擊腳本,從而得到明文密碼。固然,這和電腦中毒、鍵盤偷窺同樣,都屬於「環境有問題」,不在本文討論範圍內。本文討論的是數據庫泄露的場景。
用戶的配置愈來愈好,很多都是四核、八核處理器。可否利用多線程的優點,將慢加密計算進行分解?若是每一步計算都依賴以前的結果,是沒法進行拆解的。例如:
for i = 0 ~ 10000 x = hash(x) end
這是一個串行的計算。然而只有並行的問題,才能分解成多個小任務。不過,換一種方式的多線程也是能夠的。例如咱們使用 4 個線程:
# 線程 1 x1 = hash(password + "salt1") for i = 0 ~ 2500 x1 = hash(x1) end # 線程 2 x2 = hash(password + "salt2") for i = 0 ~ 2500 x2 = hash(x2) end # ...
最終將 4 個結果合併起來,再作一次加密,做爲慢加密結果。但這樣會致使更容易破解嗎?留着給你們思考。
前端慢加密,就是讓每一個用戶貢獻少許的計算資源,使加密變得更強勁。即便數據泄露,其中也凝聚了全網站用戶的算力,從而大幅增長破解成本。
前些年比特幣流行時,突發奇想用瀏覽器來挖礦。雖然沒作成,不過得到了一些密碼學姿式。近期從新進行了整理,並添加了一些新想法,因而寫篇詳細的文章分享一下。由於密碼學屬於傳統領域,因此結合當下流行的 Web 技術,才能更有新意。
若是你對算法有疑惑,能夠先仔細看 0×05 這節。
若是你是耐心看完本文的,但願能有收穫: )