验证码(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甚至更少!