驗證碼(CAPTCHA)一詞,幾乎是上網的人都接觸過。通俗地將,驗證碼就是一種把坐在電腦前的人類與機器區分開來的測試,也算是一種最多見反圖靈測試。通常來講,驗證碼由計算機生成,服務器端的計算機知道答案,但在網線這端,應該只有用戶(即真正的人)知道答案,而計算機不知道。html
從上面的定義裏,易得:python
當下,最流行的仍是圖形驗證碼。讓咱們來看幾個簡單的圖形驗證碼,而後分析一下它們的共同點和弱點。算法
驗證碼 | 弱點 |
![]() |
①有背景,可是色調很淡,干擾點的色調也很單一。 |
![]() |
②最弱的驗證碼,沒有任何雜點、背景等。但好久之前,不少論壇都盛行這種驗證碼。 |
![]() |
③一樣是沒有背景,可是有了顏色隨意的干擾點,略微提升了識別難度。 |
![]() |
④有雜點,有背景,並且雜點還很多。只是驗證碼主題顏色單調。 |
很顯然,②是最簡單的。識別這個驗證碼,只有一個步驟:匹配字模——直接循環匹配已有的全部的同字體的數據,找出最高的類似度(算法見附錄0x01)。可是不是全部的驗證碼程序編寫者都這麼不負責。像①③④,雖然仍是很簡單,但至少能夠難到新手們。數組
但仔細觀察能夠發現,攪局者只是那些各色的背景和一些干擾點而已。要識別,仍是很容易,只是要在②的基礎上加上如下幾步:服務器
1、圖像預處理:遍歷全部像素點,去除背景。因爲驗證碼不像國寶熊貓那麼黑白分明,其彩色的信息量極大,不便於處理,因此要二值化,是整張圖片變成只含有0與1的矩陣。獲得一個W*H的二維數組(矩陣)。app
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甚至更少!