"验证码"大家肯定都熟悉,在登录网站、论坛、应用程序的时候都会让你输入"验证码",验证码的出现主要是为了区分用户是计算机还是人的公共全自动程序,可以有效防止恶意破解密码、论坛灌水等行为发生。随着技术的发展,"黑客"们也并没有停止脚步,"验证码"也没想象中那样安全。
今天就以本篇文章内容,给大家详细介绍一下几种常见的"验证码"破解方法!
一、 了解验证码的种类
目前主流的验证码大致可分为三类:文本验证码、图像验证码、音视频验证码。
1、文本验证码
文本验证码是一种使用最广泛,历史最悠久的验证码,它展示一张含有多个字符的图片,要求用户正确识别并在输入框中输入对应的字符,输入全部正确即认证成功。这些字符多为大小写英文字母、数字或它们的组合,伴随着噪线、扭曲、多字体、空心、复杂背景等机制来提升安全性。目前也已经有大量中文文本验证码投入了线上使用。
早期的文本验证码字符集小,虽然简单明了但很容易被破解。为了提升文本验证码的安全性,设计者们向验证码中加入了一些安全机制来抵抗程序攻击,这些安全机制多是对抗分割和对抗识别的。对抗分割的安全机制增加了程序分割出单个字符的难度,比如用字符粘连(Crowding Characters Together , CCT)抵抗分割,用空心线来描绘字符轮廓(由于粘连机制使得可用性较差,因此许多网站引入空心机制增加用户友好性,使得字符在粘连的情况下仍能被较好的被用户所识别),用复杂背景隐藏嵌入的字符位置信息,增加噪线干扰,字符变长,双层字符等。对抗识别的机制增加程序识别字符的难度,比如在同一验证码字符中运用多字体并设置不同的字符大小,将字符旋转到某个随机的角度,或者将字符进行拉伸扭曲变形等。部分使用了相应安全机制的验证码如下图所示。即使文本验证码中加入了各种各样的安全机制,但仍然无法逃脱逐一被破解的命运。
2、图像验证码
图像验证码非常受研究者们的喜爱,各种有关图像验证码的研究。相比于需要键盘输入的文本验证码,图像验证码往往只需用户用鼠标进行点击、拖拽等简单操作。它要求用户理解图片内容,然后根据提示给出反馈。常见的图像验证码可分为选择类、拖拽类和拼图类。
选择类图像验证码:通常利用人的图像理解分类能力,要求用户从多个候选图像中选出所有的某类图像。如上图买火车票的12306网站验证码就是选择类图像验证码
拖拽类图像验证码:要求用户将滑块拖动到指定位置或按照固定的轨迹拖拽鼠标进行运动。图像验证码中的拖拽滑动类,比如极验验证码需要用户将滑块滑动到指定位置,京东的手势验证码要求用户根据图像上的随机轨迹完成手指的触屏操作,这类验证码目前有较大发展潜力。
拼图类图像验证码:该验证码十分新颖,比如要求用户将两块错置的拼图交换回原位或将某块拼图拼到图片中的对应位置等,提升了验证码的趣味性。拼图类验证码如Capy,要求用户将拼图块拼回图中。
3、音视频验证码
音视频验证码的使用较少,且受验证时间长的限制。音频验证码起初是作为视觉类验证码的补充品出现,面向具有视觉障碍的用户。它可分为倾听者机制和话语者机制,倾听者机制要求用户听一段内容可能是一个词或几个随机数的音频,正确键入音频内容即可验证成功,话语者机制利用的是人声与合成声之间的差异性,它要求用户读一段给定的文字来判定该用户是否合法。而视频验证码往往给用户提供一个视频文件,要求用户根据视频内容选择最合适视频的选项。
二、验证码的实现原理
验证码实现的原理:
首先了解到的是验证码是由服务端产生,以图片的形式展示在客户端或页面,用户端的用户根据图片识别验证码,并进行注册提交,提交的验证码在服务层进行校验,如果校验成功,则用户注册成功并登陆,如果失败,请用户重新注册。然后想一想这些图片是怎样生成的,验证码如何设计的,验证码为什么要设计成图片的格式,而且人们肉眼都难以识别其中的验证码。
图片是在服务端随机产生,这些图片可以通过设置规定他们的高度和宽度,然后再图片上绘制一些干扰线,当然,干扰线的数量也是可以控制的,不同验证码插件或者不同的网站验证码图片的干扰程度不同,一般来说干扰程度越高,防止恶意攻击的效果会更好一些。但是用户识别起来也会增加难度,制造干扰线也是防止别人编写程序识别图片中的验证码或者通过某种机器提取图片中的验证码,来进行恶意注册或搞破坏。如果人眼都不能一眼轻易识别图片中的验证码,相信机器的识别度在比人眼低的情况下,不容易获取图片中的验证码。
图片上的验证码可以是数组和大小写字母的组合,也可以是汉字的形式,这些字符都是随机产生并进行拼接的。实现验证功能的图片合成之后,会转换为一串字符串,然后以字节数组输出流的形式传送到前端,并显示在页面的相应位置。不是,该验证码产生的同时,会伴随产生它的唯一标志的id,过期时间,然后这些数据一般会一同封装到服务端的缓存中,到用户输入验证码并返回时,在服务端进行校验,并把校验的结果返回到前端界面。这些是验证码的大致原理。
三、常见的验证码破解思路
1.输入式验证码
这种验证码主要是通过用户输入图片中的字母、数字、汉字等进行验证。如下图
破解思路:这种是最简单的一种,只要识别出里面的内容,然后填入到输入框中即可。这种识别技术叫OCR,这里我们推荐使用Python的第三方库,tesserocr。对于没有什么背影影响的验证码如第二副图,直接通过这个库来识别就可以。但是对于有嘈杂的背景的验证码这种,直接识别识别率会很低,遇到这种我们就得需要先处理一下图片,先对图片进行灰度化,然后再进行二值化,再去识别,这样识别率会大大提高。
2.滑动式验证码
这种是将备选碎片直线滑动到正确的位置,如下图
破解思路:对于这种验证码就比较复杂一点,但也是有相应的办法。我们直接想到的就是模拟人去拖动验证码的行为,点击按钮,然后看到了缺口 的位置,最后把拼图拖到缺口位置处完成验证。
第一步:点击按钮。然后我们发现,在你没有点击按钮的时候那个缺口和拼图是没有出现的,点击后才出现,这为我们找到缺口的位置提供了灵感。
第二步:拖到缺口位置。我们知道拼图应该拖到缺口处,但是这个距离如果用数值来表示?通过我们第一步观察到的现象,我们可以找到缺口的位置。这里我们可以比较两张图的像素,设置一个基准值,如果某个位置的差值超过了基准值,那我们就找到了这两张图片不一样的位置,当然我们是从那块拼图的右侧开始并且从左到右,找到第一个不一样的位置时就结束,这是的位置应该是缺口的left,所以我们使用selenium拖到这个位置即可。这里还有个疑问就是如何能自动的保存这两张图?这里我们可以先找到这个标签,然后获取它的location和size,然后 top,bottom,left,right = location['y'] ,location['y'] size['height'] location['x'] size['width'] ,然后截图,最后抠图填入这四个位置就行。具体的使用可以查看selenium文档,点击按钮前抠张图,点击后再抠张图。最后拖动的时候要需要模拟人的行为,先加速然后减速。因为这种验证码有行为特征检测,人是不可能做到一直匀速的,否则它就判定为是机器在拖动,这样就无法通过验证了。
3.点击式的图文验证 和 图标选择
图文验证:通过文字提醒用户点击图中相同字的位置进行验证。
图标选择: 给出一组图片,按要求点击其中一张或者多张。借用万物识别的难度阻挡机器。
这两种原理相似,只不过是一个是给出文字,点击图片中的文字,一个是给出图片,点出内容相同的图片。
这两种没有特别好的方法,只能借助第三方识别接口来识别出相同的内容,推荐一个超级鹰,把验证码发过去,会返回相应的点击坐标。
然后再使用selenium模拟点击即可。具体怎么获取图片和上面方法一样。
4.宫格验证码
这种就很棘手,每一次出现的都不一样,但是也会出现一样的。而且拖动顺序都不一样。
但是我们发现不一样的验证码个数是有限的,这里采用模版匹配的方法。我觉得就好像暴力枚举,把所有出现的验证码保存下来,然后挑出不一样的验证码,按照拖动顺序命名,我们从左到右上下到下,设为1,2,3,4。上图的滑动顺序为4,3,2,1所以我们命名4_3_2_1.png,这里得手动搞。当验证码出现的时候,用我们保存的图片一一枚举,与出现这种比较像素,方法见上面。如果匹配上了,拖动顺序就为4,3,2,1。然后使用selenium模拟即可。
四、验证码破解实例分享
1、常规字符验证码破解方法
上图是最常见的字符
破解这种验证码的基本流程:
· 预处理(preprocess)
· 分割字符(split)
· 识别单个字符(classification)
预处理主要用到的是图形学的相关知识,比如:
· 二值化
· CFS
· 连通域
分割字符这里就比较麻烦了,要视具体情况而定,比如下面这两种:
对于第一种,很明显的是可以直接分割,因为根本不粘连啊,但是对于第二种你恐怕要动点心思了。
识别单个字符,这个可能是最没有技术含量的,现在大量的cnn使用,识别简单的验证码字符完全不是问题,比如caffe mnist就完全可以,当然最后的识别效果取决与你之前做的样本的好坏
预处理阶段:
首先说下我这次演示的需要破解的验证码,如下所示:
主要有上面的三种样子,我们对其观察可以知道以下事实:
(1) 大部分字符是不粘连的
(2) 字体的变化的样式基本就三种,不是很多
所以,针对验证码的特点,我的具体的破解的流程是这样的:
1) CFS获得图像块
2) NN的方法获得图像块中字符的个数
3) 平均分割图像块获得单个字符
4) 识别单个字符获得答案
可能有人会问,为何要使用NN的方法获得图像块中字符的个数? 直观上来说,包含三个字符的图像块比包含两个字符的图像块肯定要宽啊! 这个我在使用CFS获得图像块之后进行了统计,发现使用宽度来判定会产生大面积的误判,因为有的三个字符黏在一起其宽度反而比两个的要小,所以就可以使用NN了。
二值化:
顶格:
CFS:
CFS之后的处理:
对于CFS之后的图像(label_image),我们还需要进行处理,比如被包含的就不要了,质心之间靠的太近的可以合并,这里列出我们可以由label_image获得的相关信息(比如坐标,质心等):
对于验证码,采用的是三个策略:
1) 被包含的区域忽略
2) 像素少于30的区域忽略
3) 宽高等于图片的宽高的区域之间取交集
预测CFS块包含的字符数:
这里使用的是Keras,具体的安装方法可以参考ubuntu下安装Keras 的方法。
典型的长度是这样的:
1) 单个字符: 23 pixel
2) 两个字符: 59 pixel
3) 三个字符: 105 pixel
4) 四个字符: 140 pixel
5) 宽度超过145 pixel认为是4个字符
识别长度的过程是判断某个CFS块的宽度距离上述典型长度的和,找出最小值,比如某个宽度是65,则:
sum1 = (65-23) (65-59) = 43
sum2 = (65-59) (105-65) = 41
sum3 = (105-65) (140-65) = 115
最小值是41,距离59和65比较近,则猜测为2或者3个字符
分别训练三个model,用来判断长度,如下:
model 1:
Model2:
Model3:
获得字符个数之后就可以均分获得单个字符
单个字符识别:
之前也是训练了个cnn modelusing Keras,但是精度只有大概85%(48个类),后来改用Caffe mnist精度到了89%,稍好
model 4:
对于以上验证码的破解,主要的工作就是让验证码的所有字符分开,之后使用字符的单个识别就可以识别,过程中会用到图像处理、cnn的方法等。
2、破解滑块(极验)验证码
破解核心思路流程:
(1)如何确定滑块滑动的距离?
滑块滑动的距离,需要检测验证码图片的缺口位置
滑动距离 = 终点坐标 - 起点坐标
然后问题转化为我们需要屏幕截图,根据selenium中的position方法并进行一些坐标计算,获取我们需要的位置
(2)坐标我们如何获取?
起点坐标:
每次运行程序,位置固定不变,滑块左边界离验证码图片左边界有6px的距离
终点坐标:
每次运行程序,位置会变,我们需要计算每次缺口的位置
怎么计算终点也就是缺口的位置?
先举个例子,比如我下面两个图片都是120*60的图片,一个是纯色的图片,一个是有一个蓝色线条的图片(蓝色线条位置我事先设定的是60px位置),我现在让你通过程序确定蓝色线条的位置,你怎么确定?
答案:
遍历所有像素点色值,找出色值不一样的点的位置来确定蓝色线条的位置
这句话该怎么理解?大家点开下面的图片,是不是发现图片都是由一个一个像素点组成的,120*60的图片,对应的像素就是横轴有120个像素点,纵轴有60个像素点,我们需要遍历两个图片的坐标并对比色值,从(0,0)(0,1)......一直到(120,60),开始对比两个图片的色值,遇到色值不一样的,我们return返回该位置即可
下面是简单代码演示,获取缺口位置为60,跟我预先设定的位置一样,后面会详细介绍该方法如何操作
而我们目标网站的验证码图片也是类似,这是我截图的,一个是没有缺口的验证码,一个是有缺口的验证码,我们同样需要遍历,但是注意一点的是,我们这次遍历不是从图片(0,0)开始遍历,而是需要从滑块的右侧边缘开始遍历。
接下来用selenium模拟登陆,输入用户名和密码
获取验证码位置坐标,尺寸大小:
验证码需要截图,最后需要的验证码位置是上下左右的一个区域,从屏幕上根据这个区域进行截图,经过多次测试运行,发现截图区域设定为(558,215,816,374)相对合适,可以截到验证码
这一步是返回缺口位置left
这个方法是比较色值差异
计算滑动距离=119 - 边缘空隙6 = 113
做到这一步,我们得出了需要滑动113px,然后我们的滑动,需要满足物理学规律,即先加速,后减速的过程,因为人的实际操作也是这样的,刚开始先加速,到后面开始减速
我设定的减速位置为mid,也就是滑动到五分之四的位置时候,速度开始降下来
中间定义了一个加速度a,当没有到4/5距离时候,加速度为1,当滑动最后1/5距离时,加速度变为-2,该代码参考崔大神的方法,根据数学公式,计算得出滑动轨迹,就相对模拟了一种相对真实的人的滑动过程
def get_track(self, distance): """ 根据偏移量获取移动轨迹 :param distance: 偏移量 :return: 移动轨迹 """ track = [] # 当前位移 current = 0 mid = distance * 4 / 5 # 计算间隔 t = 0.2 # 初速度 v = 0 while current < distance: if current < mid: # 加速度为正1 a = 1 else: # 加速度为负2 a = -2 # 初速度v0 v0 = v # 当前速度v = v0 at v = v0 a * t # 移动距离x = v0t 1/2 * a * t^2 move = v0 * t 1 / 2 * a * t * t # 当前位移 current = move # 加入轨迹 track.append(round(move)) return track
这是计算得出的滑动轨迹
另外注意,如果拖动过程发现拖得不够,就设置为-10或者-11,让轨迹多走点,如果发现拖得过了,就设置为-12,-13,让轨迹少走点,多尝试
,