驗證碼(CAPTCHA)一詞,幾乎是上網的人都接觸過。通俗地將,驗證碼就是一種把坐在電腦前的人類與機器區分開來的測試,也算是一種最常見反圖靈測試。一般來說,驗證碼由計算機生成,服務器端的計算機知道答案,但在網線這端,應該只有用戶(即真正的人)知道答案,而計算機不知道。
從上面的定義里,易得:
- 驗證碼應該是不易被計算機識別或破解出來的,如果用簡單的算法,也能得到極高的破解率(大於或接近於75%),則這個驗證碼不算一個合格的驗證碼。
- 要攻破驗證碼,最好的方法應該是機器學習,因為這個方法能讓計算機模擬人類的行為。通過一定的樣本訓練后,機器的識別度會接近人類。
- 當然,要攻破驗證碼,找到一些沒事干的人進行分布式驗證碼人肉識別(俗稱“打碼”),也是一個不錯的方法,只不過這種方法需要投入金錢。
- 要防御破解驗證碼的攻擊,應該從其源頭想起——驗證碼不就是區別人和機器么?那么,鼠標的細微移動,鍵盤的鍵入速度,用戶(可有可能是電腦)看見驗證碼后的反應等等,都可以作為識別機器和人的證據。當然,對於驗證碼,添加一些機器能識別得到,而人類反而看不見的東西,也能很好地揪出那些干壞事的人。
- 時刻記住,驗證碼是給人看的,所以要從人的角度破解、防御驗證碼(攻擊)。
破解篇:
當下,最流行的還是圖形驗證碼。讓我們來看幾個簡單的圖形驗證碼,然后分析一下它們的共同點和弱點。
驗證碼 | 弱點 |
![]() |
①有背景,但是色調很淡,干擾點的色調也很單一。 |
![]() |
②最弱的驗證碼,沒有任何雜點、背景等。但很久以前,很多論壇都盛行這種驗證碼。 |
![]() |
③同樣是沒有背景,但是有了顏色隨意的干擾點,略微提高了識別難度。 |
![]() |
④有雜點,有背景,而且雜點還不少。只是驗證碼主題顏色單調。 |
很顯然,②是最簡單的。識別這個驗證碼,只有一個步驟:匹配字模——直接循環匹配已有的所有的同字體的數據,找出最高的相似度(算法見附錄0x01)。但是不是所有的驗證碼程序編寫者都這么不負責。像①③④,雖然還是很簡單,但至少可以難到新手們。
但仔細觀察可以發現,攪局者只是那些各色的背景和一些干擾點而已。要識別,還是很容易,只是要在②的基礎上加上以下幾步:
1、圖像預處理:遍歷所有像素點,去除背景。由於驗證碼不像國寶熊貓那么黑白分明,其彩色的信息量極大,不便於處理,所以要二值化,是整張圖片變成只含有0與1的矩陣。得到一個W*H的二維數組(矩陣)。
2、去除干擾:刪除干擾的點、線。干擾像素的特點是不連續,占用的像素點與主題有固定的差值(或許不固定,但大小關系一般固定),可以很容易地設計算法容易過濾。而如果干擾像素采用了和驗證碼正文明顯不同的顏色,則可以在第一步二值化中直接去掉。對於干擾線,則有其它的算法(見附錄0x00)。
3、字符分割:把數組里連續的字符切割成一個個獨立的字符。算法很簡單,按照列遍歷,找到一列沒有數據1的,就記錄。如果字符有旋轉的,還得根據邊緣把它再給擺直。
以④為例,這是HUSTOJ(一個著名的在線評測系統)的驗證碼,給出識別的源碼:點擊下載。
以上種種,都是極為簡單的圖形驗證碼。但谷歌這種大公司,就搞出了整我們的驗證碼,上圖如下:
這種人都難以辨認的驗證碼,機器自然很難攻破(前者識別率約為30%,后者為5%)。其實其問題出在“字符分割”這一步。由於扭曲的驗證碼都粘連在一起,所以不能很好地分割。對於這種字符的分割,最好的方法是“滴水算法”,其原理是模仿水滴從高處向低處自然滴落的過程來對字符串進行切分。具體算法可以參考:http://www.docin.com/p-891657169.html
防御篇:
攻擊驗證碼,其實也比較容易。那么,如果沒有相應的防御措施,各大網站上就會充斥着“xxx001”、“xxx002”、“xxx003”等ID,在評論里發着各種廣告。這不能被容忍!還記得上面那個讓人抓狂,讓電腦“爆炸” 的變態驗證碼么?要從驗證碼識別的角度防御驗證碼破解,可以通過字符扭曲與字符粘連做到。還有一個有效的建議:不要使用普通字符,讓驗證碼的各個部分使用不同比例的縮放或者旋轉。
跳出這個思維慣性的圈,我們是不是可以用其他的方法干掉侵略者?Google已經想出了一個很創新的想法。它們想拋棄驗證碼,使用一個“I AM NOT A ROBOT”的復選框來代替。據說,區分人類和機器之間的微妙差異,在於他/她/它在單擊之前移動鼠標的那一瞬間。
但我們沒有大公司,更不會研究出這種算法。所以,我們可以在CSS文件中加一些花招,例如翻轉圖形。真正的用戶會看見翻轉后的圖形,而計算機則只能看見原本的圖形。類似這樣的方法其實還有很多。
附錄:
0x00——干擾線去除算法:
這個算法有一個要求,就是驗證碼的干擾線與主體的顏色不同。
給出思路,代碼很容易實現。統計每種顏色出現的最左上的地方、最右下的地方。如果這兩個坐標的差值大於某個閥值,就把這種顏色去除。
0x01——字模比對算法(編輯距離):
def editpath(s1,s2): m, n = len(s1), len(s2) colsize, v1, v2 = m + 1, [], [] for i in range((n + 1)): v1.append(i) v2.append(i) for i in range(m + 1)[1:m + 1]: for j in range(n + 1)[1:n + 1]: cost = 0 if s1[i - 1] == s2[j - 1]: cost = 0 else: cost = 1 minValue = v1[j] + 1 if minValue > v2[j - 1] + 1: minValue = v2[j - 1] + 1 if minValue > v1[j - 1] + cost: minValue = v1[j - 1] + cost v2[j] = minValue for j in range(n + 1): v1[j] = v2[j] return v2[n]
這就是一個經典的編輯距離算法,時間復雜度為O(len(str1)*len(str2)),在大常數的python下跑算法,實在很慢。考慮到我們並不是嚴格地計算編輯距離是幾,只是在一些字模中取出最值罷了、所以可以加一個優化:
while (data[k1]==(vchash[i])[k1])and(k1<len(data)-1): k1+=1; while (data[k2]==(vchash[i])[k2])and(k2>0): k2-=1; if k1-k2>=0: return i tmps=editpath((vchash[i])[k1:k2],data[k1:k2])
代碼中的tmps可以與min進行比對。經測試,這種優化可以減少時間至原來的1/6甚至更少!