zoukankan      html  css  js  c++  java
  • 验证码识别<1>

    1. 引子

            前两天访问学校自助服务器()缴纳网费,登录时发现这系统的验证码也太过“清晰”了,突然脑袋里就蹦出一个想法:如果能够自动识别验证码,然后采用暴力破解的方式,那么密码不是可以轻易被破解吗?

    ps:用户名就是学号,可以轻易获得,而密码是系统随机生成的6位数,组合方式仅有 10^6种,假设每次尝试须要50ms,那么大概需要14个小时,如果采用多线程,多个虚拟机(java)同时工作,估计把所有密码过一遍不会超过1个小时,这效率还凑合吧。。。

    image

    2. 分析

            问题的关键就在于验证码识别,至于如何请求服务器,用java分分钟搞定。学习了一些网友写的关于验证码识别的blog,如:http://blog.csdn.net/problc/article/details/5794460。发现它的基本步骤就是:【去噪】、【分割】、【匹配】,【识别】。

           ① 去噪

                即去除背景和干扰线,并且将背景置为白色,文字置为黑色,便于后面匹配。验证码获取地址:http://202.118.166.244:8080/selfservice/common/web/verifycode.jsp。通过观察会发现,文字部分颜色较深(r,g,b基本小于110),干扰部分颜色较浅。于是可以这样【去噪】:

    public static BufferedImage denoising(BufferedImage image) {
            for (int x = 0; x < image.getWidth(); x++) {
                for (int y = 0; y < image.getHeight(); y++) {
                    Color color = new Color(image.getRGB(x, y));
                    int red = color.getRed();
                    int green = color.getGreen();
                    int blue = color.getBlue();
                    if (red > 105 && green > 105 && blue > 105) {
                        image.setRGB(x, y, Color.WHITE.getRGB());
                    } else {
                        image.setRGB(x, y, Color.BLACK.getRGB());
                    }
                }
            }
            return image;
    }

    看看效果:

    处理前:verifycode_src 处理后:verifycode ,效果还是不错的!

            ② 分割

                分割很简单,将验证码按文字等分。

    /**
         * 分割图片
         * 
         * @param img
         * @param splitNum
         * @return
         * @throws IOException
         */
        public static List<BufferedImage> splitImage(BufferedImage img, int splitNum) throws IOException {
            int width = img.getWidth();
            int height = img.getHeight();
            int splitWidth = width / splitNum;
            List<BufferedImage> bufferedImages = new ArrayList<BufferedImage>();
            for (int i = 0; i < splitNum; i++) {
                bufferedImages.add(img.getSubimage(i * splitWidth, 0, splitWidth, height));
            }
            return bufferedImages;
        }

            ③ 匹配

                在匹配之前,要利用前面的两个方法得到所有字符的片段,用于匹配。像这样:

    image

                然后设计匹配算法,这一步比较关键,匹配算法的好坏将直接导致识别的正确与否。因为观察到文字都没有进行旋转,因此这里采用:用一个集合记录下图片每一纵行所拥有的黑色像素点的个数(没有像素的纵行不记录),将这个集合作为对应图片的指纹。然后分割好的验证码片段与上面的标准片段进行一一比对,最后组合在一起,从而可以识别出验证码。

    /**
         * 单个字符进行匹配
         * 
         * @param img
         * @param regularDataList
         * @return
         */
        public String matchSingleWord(BufferedImage img, List<List<Integer>> regularDataList) {
            String result = null;
            int maxRank = 0;
            List<Integer> matchedData = getFingerprint(img);
            for (int i = 0; i < regularDataList.size(); i++) {
                int rank = 0;
                List<Integer> regularData = regularDataList.get(i);
                int minColumn = Math.min(regularData.size(), matchedData.size());
                for (int j = 0; j < minColumn; j++) {
                    if (matchedData.get(j) == regularData.get(j)) {
                        rank++;
                    }
                }
                if (rank > maxRank) {
                    maxRank = rank;
                    result = i + "";
                }
            }
            return result;
        }
    /**
         * 获取图像"指纹"
         * 
         * @param image
         * @return
         */
        private static List<Integer> getFingerprint(BufferedImage image) {
            List<Integer> list = new ArrayList<Integer>();
            for (int x = 0; x < image.getWidth(); x++) {
                int count = 0;
                for (int y = 0; y < image.getHeight(); y++) {
                    // System.out.println(image.getRGB(x, y));
                    if (image.getRGB(x, y) == 0xFF000000) {
                        count++;
                    }
                }
                if (count != 0) {
                    list.add(count);
                }
            }
            return list;
        }
    /**
         * 加载作为标准的指纹List
         * 
         * @return
         * @throws IOException
         */
        private static List<List<Integer>> loadMatchDataList() throws IOException {
            List<List<Integer>> matchData = new ArrayList<List<Integer>>();
            File dir = new File("C:\Users\Administrator\Desktop\verifycode\match");
            File[] files = dir.listFiles();
            for (File file : files) {
                matchData.add(getFingerprint(ImageIO.read(file)));
            }
            return matchData;
        }

            ④ 识别

                将以上识别出的单个字符组合在一起,就得到验证码啦。

    public static void main(String[] args) throws Exception {
            BufferedImage image = ImageIO.read(new URL("http://202.118.166.244:8080/selfservice/common/web/verifycode.jsp"));
            ImageIO.write(image, "png", new File("C:\Users\Administrator\Desktop\verifycode\verifycode_src.png"));
            image = denoising(image);
            // 注意:最好以png格式输出,否则可能导致图片失真
            ImageIO.write(image, "png", new File("C:\Users\Administrator\Desktop\verifycode\verifycode.png"));
            List<BufferedImage> images = splitImage(image, 4);
            List<List<Integer>> regularFingerprintList = loadMatchDataList();
            String result = "";
            for (BufferedImage bufferedImage : images) {
                result += matchSingleWord(bufferedImage, regularFingerprintList);
            }
            System.out.println("验证码是:" + result);
        }

            结果:imageimage ,完全正确。

    3. 总结

        总的来说,由于该类型验证码本生较为简单,所以处理起来十分顺利。但不管验证码怎么变化,基于这种识别算法的基本就是以上几部,具体做法根据具体案例实现。

        最后随便搞一个账号来测试,用时2个多小时跑出了密码。。。

        先写到这里,以后再研究其他识别算法。

  • 相关阅读:
    POJ2528——Mayor's posters (线段树区间更新查询+离散化)
    C++STL——unique函数总结
    HDU 5618 Jam's problem again(CDQ分治+树状数组(三维模板题))
    c++解决爆栈,手动加栈!
    POJ1741——Tree (树分治之点分治)
    树分治之点分治模板总结
    CodeForces
    字典树
    卡特兰数高精度算法
    基数排序
  • 原文地址:https://www.cnblogs.com/dongkuo/p/4781653.html
Copyright © 2011-2022 走看看