浅谈实现滑动验证码,Java核心代码以及原理分析

原创
2018/01/29 21:36
阅读数 9.3K

一、背景

早期的互联网是没有验证码的,随着后来计算机程序的发展,黑客编写了模仿登录、恶意破解密码、刷票、论坛灌水等恶意程序,破坏了整个网络的平衡性。于是验证码这种验证是否是人工操作的检验机制便产生了。随着验证码的出现,它可以防止:恶意破解密码、刷票、论坛灌水,有效防止某个黑客对某一个特定注册用户用特定程序暴力破解方式进行不断的登陆尝试等等,给互联网带来了一定的安全性。

科技在进步,黑客也在进步。验证码在发展的过程中遭遇了很大的批评。早期的图片验证码在随着OCR技术的发展,已越来越不可靠。因此新一代的验证码就诞生了。例如:12306的选图验证码,淘宝的滑动验证码,极验验证码,短信验证码,语音验证码等。这些验证码的效果都不是很理想,比较繁琐。而淘宝推出的滑动验证码,深受大家好评。

二、验证码分类以及分析

1.Gif动画验证码

主流验证码通过提供静态的图片,比较容易被OCR软件识别,有的网站提供GIF动态的验证码图片, 使得识别器不容易辨识哪一个图层是真正的验证码图片,可以提供清晰的图片的同时,可以更有效得防止识别器的识别,据统计,动画gif验证码的防垃圾注入可以达到100%,是一个非常有效的验证码创新模式。同时gif动画效果可以有多达百种,也可以增加网站页面的美观效果。

2.手机短信验证码

手机验证码是通过发送验证码到手机,大型网站尤其是购物网站,都提供有手机短信验证码功能,可以比较准确和安全地保证购物的安全性,验证用户的正确性,是最有效的验证码系统。某些验证码接入商提供手机短信验证码服务,各网站通过接口发送请求到接入商的服务器,服务器发送随机数字或字母到手机中,由接入商的服务器统一做验证码的验证。

3.手机语音验证码

您是否遇到过您的网站用户或会员经常因为各种原因收不到网站的验证码,或新用户注册收不到短信没有耐心就直接放弃了您的网站,或因为验证码的问题体验不佳流失掉老客户和潜在新客户呢? 现在,语音验证码的出现把这些问题一下统统解决了,您再也不用为验证码的问题而烦恼了。只要用户的手机或座机能正常接听电话,就一定能收到语音验证码,验证码实现自动语音播报,同时短信也能同时发送到用户手机,实现双保险确保万无一失。语音验证码如果有拨通失败的,系统还能自动重播,确保不漏掉任何一个,从根本上解决您的网站用户收不到验证码的问题。

4.视频验证码

视频验证码是验证码中的新秀,视频验证码中随机数字、字母和中文组合而成的验证码动态嵌入到MP4,flv等格式的视频中,增大了破解难度。验证码视频动态变换,随机响应,可以有效防范字典攻击、穷举攻击等攻击行为。视频中的验证码字母、数字组合,字体的形状、大小,速度的快慢,显示效果和轨迹的动态变换,增加了恶意抓屏破解的难度。其安全度远高于普通的验证码,而且这种验证码形式使用户不会感到枯燥,由于其提高了机器识别的难度从而可以降低用户识别的难度,使得用户更容易辨认。

5.滑动验证码

滑动验证码产生于最近两三年,由于12306的选图验证码实现太过复杂,成本太高,不可大力推广,而且一定程度上加大了买票的力度,饱受非议。因此淘宝推出了滑动验证码以及后来的极验验证码。

三、滑动验证码实现原理

滑动验证码在很多网站逐步流行起来,一方面对用户体验来说,比较新颖,操作简单,另一方面相对图形验证码来说,安全性并没有很大的降低。当然到目前为止,没有绝对的安全验证,只是不断增加攻击者的绕过成本。

综合来讲:滑动验证码是根据用户在滑动滑块的响应时间,拖拽速度,时间,位置,轨迹,重试次数等来评估风险。其交互时序图,可以如下概括:

滑动验证码时序交互图

四、滑动验证码核心流程分析

1.服务端随机生成抠图和带有抠图阴影的背景图片,服务端保存随机抠图位置坐标;

2.前端实现滑动交互,将抠图拼在抠图阴影之上,获取到用户滑动距离值;

3.前端将用户滑动距离值传入服务端,服务端校验误差是否在容许范围内;

备注说明:单纯校验用户滑动距离是最基本的校验,处于更高安全考虑,可以考虑用户滑动整个轨迹、用户在当前页面上的行为等,可以将其细化复杂地步,可以根据实际情况设计。亦或借助用户行为数据分析模型,最终的目标都是增加非法的模拟和绕过的难度。

五、滑动验证码分析与论证

A.分析

滑动图形验证码由抠块和带有抠块阴影的原图两部分组成,但是里面包含了两个重要特性保证被暴力破解的难度:

1.抠块的形状随机;

2.抠块所在原图的位置随机;

如此就可以在有限的图集中制造出随机的、无规律可寻的抠图和原图的配对。

B.产生随机形状

1.确定抠图轮廓

大家都知道图片是有像素组成,每个像素点对应一种颜色,颜色可以用RGB形式表示,外加一个透明度,由此可以抽象下模型如下:

把一张图理解成一个平面图形,左上角为原点,向右x轴,向下y轴,每个坐标值对应该位置像素点的颜色,由此就可以把一张图转换成一个二维数组。基于这个考虑,轮廓也用二维数组来表示,轮廓内元素值为1,轮廓外元素值对应0。

2.轮廓形状确定

有坐标系、有矩形、有圆形,由此用数学中解析几何思想,用圆的函数方程和矩形的边线的函数,具体如下:

(x-a)²+(y-b)²=r²中,其中三个参数a、b、r,即圆心坐标为(a,b),半径r。这些将抠图放在上文所述的坐标系上很容易就图算出来具体的值。

用Java代码实现如下:

      /**
         * 获取图形值
         * [@return](https://my.oschina.net/u/556800)  int[][]
      */
    private int[][] getGraphValue() {
            int[][] data = new int[targetLength][targetWidth];
            double x2 = targetLength - circleR - 2;
            //随机生成圆的位置
            double h1 = circleR + Math.random() * (targetWidth - 3 * circleR - r1);
            double  squareValue = circleR * circleR;
    
            double pointX = targetLength - circleR - r1;
            double pointY = targetWidth - circleR - r1;
    
            int i = 0;
            while (i < targetLength) {
                for (int j = 0; j < targetWidth; j++) {
                    //右边○
                    double d3 = Math.pow(i - x2, 2) + Math.pow(j - h1, 2);
    
                    if (d1 <= squareValue || (j >= pointY && d2 >= squareValue) || (i >= pointX && d3 >= squareValue)) {
                        data[i][j] = 0;
    
                    } else {
                        data[i][j] = 1;
                    }
                }
                i++;
            }
            return data;
    }

3.有这个轮廓后就可以依据这个二维数组的值来判定抠图并在原图上抠图位置处加阴影。

用Java代码实现如下:

    /**
         * 在原始图片上添加阴影层
         * [@param](https://my.oschina.net/u/2303379) originalImage  原始图片
         * [@param](https://my.oschina.net/u/2303379) targetImage    目标图片
         * [@param](https://my.oschina.net/u/2303379) templateImage 临时图片
         * [@param](https://my.oschina.net/u/2303379) pointX   坐标x
         * @param pointY   坐标 y
    */
    private void addOriginalShadow(BufferedImage originalImage, BufferedImage targetImage, int[][] templateImage, int pointX, int pointY) {
            int i = 0;
            while (i < targetLength) {
                int j = 0;
                while (j < targetWidth) {
                    int valRGB = templateImage[i][j];
                    // 原图中对应位置变色处理
                    int originalPictureRGB = originalImage.getRGB(pointX + i, pointY + j);
    
                    if (valRGB == 1) {
                        //抠图上复制对应颜色值
                        targetImage.setRGB(i, pointY + j, originalPictureRGB);
                        int r = (0xff & originalPictureRGB);
                        int g = (0xff & (originalPictureRGB >> 8));
                        int b = (0xff & (originalPictureRGB >> 16));
                        originalPictureRGB = r + (g << 8) + (b << 16) + (200 << 24);
                        //原图对应位置颜色变化
                        originalImage.setRGB(pointX + i, pointY + j, originalPictureRGB);
                    }
                    j++;
                }
                i++;
            }
    }

备注:加阴影的方式需要自己再具体优化调整下,修改轮廓内的颜色值,比如按一个什么私密算法加黑或者加白,轮廓颜色跟周围做进一步融合等等,另外用户还能看出来。

4.经过上面两步后就得到了抠图和带抠图阴影的原图。为增加混淆和提高网络加载效果,还需要对图片做进一步处理,一般需要处理2步即可:

a.图片做模糊处理增加机器识别难度

b.适当同质量压缩

这里处理模糊方式,我们就采用高斯模糊,至于高斯模糊原理了解去Google,实现版本也有很多种,这里介绍一种不依赖任何第三方包实现,用Java实现如下:

//高斯图片模糊处理并加工
public static ConvolveOp getGaussianBlurFilter(int radius, boolean horizontal) {
    if (radius < 1) {
        throw new IllegalArgumentException("Radius must be >= 1");
    }

    int size = radius * 2 + 1;
    float[] data = new float[size];

    float sigma = radius / 3.0f;
    float twoSigmaSquare = 2.0f * sigma * sigma;
    float sigmaRoot = (float) Math.sqrt(twoSigmaSquare * Math.PI);
    float total = 0.0f;

    for (int i = -radius; i <= radius; i++) {
        float distance = i * i;
        int index = i + radius;
        data[index] = (float) Math.exp(-distance / twoSigmaSquare) / sigmaRoot;
        total += data[index];
    }
    

    for (int i = 0; i < data.length; i++) {
        data[i] /= total;
    }

    Kernel kernel;
    if (horizontal) {
        kernel = new Kernel(size, 1, data);
    } else {
        kernel = new Kernel(1, size, data);
    }
    return new ConvolveOp(kernel, ConvolveOp.EDGE_NO_OP, null);
}

public static void simpleBlur(BufferedImage source, BufferedImage dest) {
    BufferedImageOp op = getGaussianBlurFilter(2, false);
    op.filter(source, dest);
}

图片同质量压缩处理,用Java实现如下:

    /**
     * 图片压缩处理
     * @param bufferedImage   图片对象
     * @param imagType  图片类型
     * @return byte[]
     * @throws IOException
     */
    public static byte[] compressPictures(BufferedImage bufferedImage,String imagType) throws IOException {
        bos.reset();
        // 得到指定Format图片的writer
        Iterator<ImageWriter> iter = ImageIO.getImageWritersByFormatName(imagType);
        ImageWriter writer = (ImageWriter) iter.next();    
        // 获取指定writer的输出参数设置(ImageWriteParam )
        ImageWriteParam imageWriteParam = writer.getDefaultWriteParam();
        imageWriteParam.setCompressionMode(ImageWriteParam.MODE_EXPLICIT); // 设置可否压缩
        imageWriteParam.setCompressionQuality(1f); // 设置压缩质量参数    
        imageWriteParam.setProgressiveMode(ImageWriteParam.MODE_DISABLED);  
        ColorModel colorModel = ColorModel.getRGBdefault();
        // 指定压缩时使用的色彩模式
        imageWriteParam.setDestinationType(new javax.imageio.ImageTypeSpecifier(colorModel,colorModel.createCompatibleSampleModel(16, 16)));  
        writer.setOutput(ImageIO.createImageOutputStream(bos));
        IIOImage iIamge = new IIOImage(bufferedImage, null, null);
        writer.write(null, iIamge, imageWriteParam);
        return  bos.toByteArray();
    } 

至此滑动验证码核心的代码处理流程已全部结束,本文只是一种思路实现起到抛砖引玉作用,具体设计可以根据实际情况进行设计与加工,让体验更好更安全。

展开阅读全文
加载中

作者的其它热门文章

打赏
0
0 收藏
分享
打赏
2 评论
0 收藏
0
分享
返回顶部
顶部