EtherDream · 2015/12/29 18:27html
驗證碼的初衷是人機識別。不過大多時候,只是用來增長一些時間成本,下降頻率而已。前端
若是僅僅是爲了消耗時間,可否不用圖片,徹底用程序來實現?git
先來思考一個問題:寫一個能消耗對方時間的程序。github
消耗時間還不簡單,休眠一下就能夠了:算法
#!cpp
Sleep(1000)
複製代碼
這確實消耗了時間,但並無消耗 CPU。若是開了變速齒輪,瞬間就能完成。後端
要消耗 CPU 也不難,寫一個大循環就能夠了:瀏覽器
#!cpp
for i = 0 to 1000000000
end
複製代碼
不過這和 Sleep 並沒有本質區別。對方究竟有沒有運行,咱們從何得知?安全
因此,咱們須要一個返回結果 —— 只有完整運行纔有正確答案。性能優化
#!cpp
result = 0
for i = 0 to 1000000000
result = result + i
end
return result
複製代碼
經過返回的結果,咱們就能判斷對方是否完整的運行了程序。session
不過上面這個問題,畢竟仍是 too simple。小學生都知道,用數列公式就能夠直接算出結果,根本不用花時間去跑循環。
那麼有什麼算法,是沒法用公式推測的?
顯然,單向散列函數就是。例如一個經典的問題:
#!cpp
MD5(X) == X
複製代碼
就沒法用公式來解決了。要找出答案,只能一個個窮舉過去,從而花費大量時間。
但對於驗證者,只需將收到的答案,計算一次就可判斷對錯,所以可輕易校驗。
這就是 PoW(Proof-of-Work),用來證實對方投入工做的方法。
固然,上面的例子太困難了,並且答案能夠重複使用,因此還需改進。例如:
#!cpp
MD5("問題" + X) == "0000......"
複製代碼
咱們只要求散列結果的前幾位是 0 就能夠。這樣位數越小,答案就越容易找到。同時增長一個鹽值,讓答案不能重複使用。
事實上,比特幣就用到了相似的方式,使用了 SHA-256 做爲散列函數。這樣只能窮舉,沒法用更快的方法投機取巧,體現了挖礦工做的價值。
用散列函數實現的 PoW,就叫 Hashcash。
Hashcash 早不是新鮮事,曾經在反垃圾郵件中就已使用。
例如用戶寫完郵件時,客戶端將「收件地址 + 郵件內容」做爲 Salt,而後計算符合條件的答案:
#!cpp
Hash(X, Salt) == "000000..."
複製代碼
最後將找到的 X 附加在郵件中併發送。服務端收到後,便可鑑定發送這封郵件,是否花費了計算工做。
對於正經常使用戶來講,額外的幾秒並不影響使用;但對於製造垃圾郵件的人,就大幅增長了成本。
傳統策略,大多經過 IP、帳號等限制。攻擊者能夠用大量的馬甲和代理,來繞過這些限制。
而使用了 PoW,就把瓶頸限制在硬件上 —— 計算有多快,操做才能多快。
一樣的,Hashcash 也能用於 Web。例如論壇,可在發帖時計算:
#!cpp
Hash(X, 帖子內容) == "000000..."
複製代碼
不過,不一樣於郵件客戶端可在後臺自動計算,發帖時若是卡上好幾秒,將會大幅下降用戶體驗。
所以不能選擇 帖子內容、標題 等這些用戶輸入做爲鹽值。而是用傳統驗證碼的方式,後端下發一個隨機數。
前端使用這個隨機數做爲鹽值 —— 這樣頁面打開時,就能夠開始計算了。
#!cpp
# 後端 - 分配
session["pow_code"] = rand()
# 前端 - 挖礦
while Hash(X, pow_code) == "000000..."
X = X + 1
end
複製代碼
咱們選擇一個適中的難度,例如 10 秒。經過多線程,還能夠更快的完成計算任務,同時不影響用戶體驗。
正常狀況下,用戶發帖前就已計算完成。提交時,將其附帶上。
若是提交時還未算出,則等待計算完成。(發帖太快,有灌水嫌疑)
#!cpp
# 前端 - 提交
wait X
submit(..., X)
# 後端 - 校驗
if Hash(X, session["pow_code"]) == "000000..."
ok
else
fail
end
複製代碼
這樣,就實現了一個「測試機器算力」的驗證碼。
目前已有提供 hashcash 第三方驗證的網站,例如 hashcash.io。
固然在 Web 中使用,性能也是一大問題。若是 10 秒的腳本計算,用本地程序只需 1 秒,那攻擊者就可使用本地版的外掛了。
好在現在有 asm.js,可接近原生性能;對於較老的瀏覽器,也可使用 Flash 做後補。在上一篇文章 0x08 節 中已詳細講解。
若是算力實在不夠,也可使用後備方案 —— 傳統圖形驗證碼。
這樣,高性能用戶可享受更好的體驗,低性能用戶也能保障基本功能。
這也算是鼓勵你們使用現代瀏覽器吧:)
不過,語言上的性能差距仍是有限的,外掛不會糾結於此,而是使用更強力的武器 —— GPU。
Hashcash 的本質就是跑 hash,這是 GPU 最擅長的。例如著名的 oclHashcat,和 CPU 徹底不在一個數量級。
對抗硬件的並行計算,大體有以下方案和思路:
前 3 個在上一篇文章 0x09 節 提到了,下面討論一些不一樣的。
若是咱們也能在 Web 中調用顯卡計算,那 GPU 版的外掛就毫無優點了。
不過,這個想法彷佛有些遙遠。儘管目前主流瀏覽器都支持 WebGL,但都只侷限於渲染加速上,並未提供通用計算接口。
固然,也能夠經過一些 hack 的方式,例如曾有人嘗試用 WebGL 挖比特幣,但效率並不高。
若是將來 WebCL 成爲標準,或許還能考慮。
上回討論慢加密時,曾提到爲何要性能優化。由於本身創造加密算法是不推薦的,因此得優化現有的算法。
不過,相比帳號安全,驗證碼的要求則低得多,並且隨時能夠更換算法,所以不妨本身來創造一個。
自創的加密算法,強度顯然沒有保障。但咱們能夠從「隱蔽性」上着手 —— 將代碼混淆到難以讀懂,這時,考驗對方的則是逆向能力了。
這和以前寫的《對抗假人 —— 先後端結合的 WAF》有點相似。不過,若是混淆能作到足夠好,還須要 PoW 機制嗎?
有勝於無。由於瀏覽器指紋、用戶行爲等信息,都是能夠經過沙盒模擬的。而工做量計算,必須消耗硬件資源,才能得出結果。
所以,使用了 PoW 就能增長攻擊者一些硬件成本。
Hashcash 的原理,決定了它是能夠並行計算的。有什麼樣的算法,是沒法並行計算的?
若是每次計算都依賴上次結果,就沒法並行了。例如以前討論的 slowhash:
#!cpp
function slowhash(x)
for i = 0 to 1000000000
x = hash(x)
end
return x
end
複製代碼
這種串行的計算,天然是沒法拆分的。但能用到 PoW 上嗎?
顯然不行!由於 PoW 雖然計算困難,但得 容易鑑定。而這種方式,鑑定時也得重複算一遍,成本太大了。
但在現實中,只要設計得當,仍是能夠嘗試的 —— 咱們使用相似 UGC 的模式,讓用戶來貢獻算力!
首先須要一個訪問量較大的網站,在其中悄悄放置一個腳本。利用在線的用戶,來生成問題和答案。
#!cpp
# 隱蔽的腳本
Q = rand()
A = slowhash(Q)
submit(Q, A)
複製代碼
固然,這項工做必須足夠隱蔽,防止被好奇的用戶發現,提交錯誤的答案。
當後端題庫有必定的積累時,就可使用驗證碼的模式了。用戶訪問時,後端從題庫中隨機抽取一個問題,安排給前端計算:
#!cpp
# 後端 - 分配問題
Q = select_key_from_db()
session["pow_ques"] = Q
# 前端 - 計算問題
A = slowhash(Q)
複製代碼
用戶提交時,後端無需任何計算,直接經過查表,便可判斷答案是否正確:
#!cpp
# 前端 - 提交
submit(..., A)
# 後端 - 鑑定
Q = session["pow_ques"]
if A == db[Q]
ok
else
fail
end
複製代碼
使用預先計算的方式,避免了繁重的鑑定工做。同時,把計算交給用戶來完成,可大幅節省硬件成本。
固然,這種模式還有不少須要考慮的地方,這裏只是介紹下基本思路,之後再詳細討論。
相比 hashcash 題解時間有必定的隨機性,slowhash 的時間是固定的,所以難度更可控。
由於 Hashcash 比較簡單,因此這裏演示一個 md5 版的,使用 asm.js 和 flash 實現,並對算法作了必定優化。
若是想看詳細的算力速度,能夠查看這個 Demo:
www.etherdream.com/FunnyScript…
看起來好像不慢,不過對比 GPU 的速度 就相形見絀了。因此,使用經典算法的 Hashcash,簡直就是不堪一擊的。
至於串行模式的 PoW,涉及到不少策略和數據積累,本文就演示了,下回單獨討論。
最後來對比下,算力驗證和傳統圖形驗證的區別。
驗證方式 | 驗證對象 | 用戶體驗 | 攔截假人 | |
---|---|---|---|---|
傳統驗證 | 圖像識別 | 人腦 | 須要交互 | 部分攔截 |
算力驗證 | 問題解答 | 電腦 | 無感知 | 沒法攔截 |
論效果,固然仍是傳統的圖形驗證更好,但這是以犧牲用戶體驗爲代價的。
硬件在不斷的發展,識圖軟件會愈來愈強大。而人腦始終是有限的,優點會愈來愈小,最終致使驗證碼愈來愈複雜。
可是算力驗證則不一樣。硬件的發展,也會帶動瀏覽器的算力提高,最終只需將問題難度調高便可。
固然,安全防護涉及的領域越多越好。每個方案都不是無敵的,都只是爲了增長一些攻擊成本而已。
因此算力驗證,結合傳統防護方案,才能出發揮價值。