验证码识别
http://blog.csdn.net/zhangjianying/article/details/2314752
现在大多数网站都采用了验证码来防止暴力破解或恶意提交。但验证码真的就很安全吗?真的就不能被机器识别??
我先讲讲我是怎么实现站外提交留言到一个网站的程序。
这个网站的留言版大致如下:
我一看这种简单的4位数字验证码,马上就感觉到有戏了。直觉告诉我让电脑来识别这些图片验证码据对简单o(∩_∩)o...
首先我马上在这个页面用右键菜单看源代码
知道验证码获取页面后 你可以直接用 http://www.XXXX.com/imgchk/validatecode.asp 这样去访问你会发现你打开的就是一个验证码图片。
对的其实返回的就是图片文件的2进制流而已。接着先用右键保存一张验证码的图片。因为要开始分析这张图片了,什么用什么工具?PhotoShop????不用就一般的画图工具就可以了。我们要搞清楚的是 这几个数字分别占几个像素就可以了。
可以看出 一个数字5*9 也就是45个像素。恩 这就可以了 另外我们可以看出 默认区域就是白色
(姑且说是白色因为我们肉眼看就是白色)
那么我的程序识别原理就是固定去扫描这45个像素点。看每个点的颜色是不是和默认的颜色一致
一致的话就标记为0 ,不一致就标记为1 。
如一个数子是2 那么我的程序扫描出来的图像就应该是:
011110
100001
000001
000001
000010
000100
001000
010000
100000
111111
如果一个数字是7那么扫描出来的图像就是:
111111
100001
000010
000010
000100
000100
001000
001000
010000
010000
恩,就这么简单呵呵。下面给出图像 扫描的java类 (不好意思,在我会的语言里面除开java就剩sql了)
package com.util;
//~--- JDK imports ------------------------------------------------------------
import com.sun.image.codec.jpeg.JPEGCodec;
import com.sun.image.codec.jpeg.JPEGEncodeParam;
import com.sun.image.codec.jpeg.JPEGImageEncoder;
import java.awt.*;
import java.awt.image.*;
import java.io.*;
import java.io.FileOutputStream;
import java.io.OutputStream;
import java.net.*;
import javax.imageio.*;
import javax.imageio.ImageIO;
/**
* 登陆验证图片转换为数字
*
*
* @version 1.0, 08/04/20
* @author 张健滢
*/
public class ImgIdent {
// 数字字符比特表
private final long[][] NUMERIC = {
{ 512104545, 562436190 }, // '0'
{ 148931080, 136348222 }, // '1'
{ 511971394, 69273663 }, // '2'
{ 511971406, 17045598 }, // '3'
{ 35168914, 586948743 }, // '4'
{ 1065486398, 17045598 }, // '5'
{ 239208494, 830871646 }, // '6'
{ 1065623684, 69239824 }, // '7'
{ 512104542, 562436190 }, // '8'
{ 512104547, 486805660 }
}; // '9'
// 字框高
private int intCharHeight = 10;
// 字框横向间隙
private int intCharSpaceH = 5; // 字框纵向间隙 private int intCharSpaceY = 1; // 字框宽 private int intCharWidth = 5; private int IntImgHeight; private BufferedImage img; private int intBgColor; private int intCharColor; private int intImgWith; private int intMaxX; private int intMaxY; private int intMinX; private int intMinY; // 座标原点 private Point pOrigin; private String strNum; /** * Constructs ... * * * @param img * * @throws IOException */ public ImgIdent(BufferedImage img) throws IOException { this.img = img; init(); } /** * 构造函数 * @param file 本地文件 * @throws IOException */ public ImgIdent(File file) throws IOException { img = ImageIO.read(file); init(); } /** * 构造函数 * @param url 远程文件 * @throws IOException */ public ImgIdent(URL url) throws IOException { img = ImageIO.read(url); init(); } /** * 类初始工作 */ private void init() { // 得到图象的长度和宽度 intImgWith = img.getWidth(); IntImgHeight = img.getHeight(); // 得到图象的背景颜色 intBgColor = img.getRGB(7, 4); // System.out.println(intBgColor); // 初始化图象原点座标 pOrigin = new Point(0, 0); } /** * Method description * */ private void getBaseInfo() { System.out.println(intBgColor + "|" + intCharColor); System.out.println(intMinX + "|" + intMinY + "|" + intMaxX + "|" + intMaxY); } /** * 得到字符的左上右下点座标 * @param intNo int 第n个字符 * @return int[] */ private Point[] getCharRange(int intNo) { // 左上右下点座标 Point pTopLeft = new Point(0, 0); Point pBottomRight = new Point(0, 0); // 左上点 pTopLeft.x = pOrigin.x + intCharWidth * (intNo - 1) + intCharSpaceH * (intNo - 1); pTopLeft.y = pOrigin.y; // 右下点 pBottomRight.x = 1 + pOrigin.x + intCharWidth * intNo + intCharSpaceH * (intNo - 1) - 1; pBottomRight.y = pOrigin.y + intCharHeight - 1; return new Point[] { pTopLeft, pBottomRight }; } /** * 与背景颜色比较返回相应的字符 * @param x int 横座标 * @param y int 纵座标 * @return char 返回字符 */ private char getBit(int x, int y) { int intCurtColor; intCurtColor = img.getRGB(x, y); // System.out.println("[" + x + "," + y + "]" + intCurtColor + "==" + intBgColor + "==>" + (Math.abs(intCurtColor) >7308252));// return (Math.abs(intCurtColor) >= 5689325)// ? '0'// : '1'; return (intCurtColor == intBgColor) ? '0' : '1'; // 5689325 6008535 } /** * 得到第n个字符对应的字符串 * @param intNo int 第n个字符 * @return String 代表字符位的串 */ private String getCharString(int intNo) { // 本字符的左上右下点座标 Point[] p = getCharRange(intNo); Point pTopLeft = p[0]; Point pBottomRight = p[1]; // 换算边界值 int intX1, intY1, intX2, intY2; intX1 = pTopLeft.x; intY1 = pTopLeft.y; intX2 = pBottomRight.x; intY2 = pBottomRight.y;// System.out.println("intX1=" + intX1);// System.out.println("intY1=" + intY1);// System.out.println("intX2=" + intX2);// System.out.println("intY2=" + intY2); // 在边界内循环取象素 int i, j; String strChar = ""; for (i = intY1; i <= intY2; i++) { for (j = intX1; j <= intX2; j++) { System.out.print(getBit(j, i)); strChar = strChar + getBit(j, i); } System.out.println(); } System.out.println(); return strChar; } /** * 得到第n个字符对应数值 * @param intNo int 第n个字符 * @return int 对应数值 */ public int getNum(int intNo) { // 取得位字符串 String strChar = getCharString(intNo); // System.out.println(intNo+"=="+strChar); // 取得串高位串和低位串 String strCharHigh = strChar.substring(0, strChar.length() / 2); String strCharLow = strChar.substring(strChar.length() / 2); // 计算高位和低位值 long lCharHigh = Long.parseLong(strCharHigh, 2); System.out.println(lCharHigh); long lCharLow = Long.parseLong(strCharLow, 2); System.out.println(lCharLow); // 在数字中循环比较 int intNum = '*'; for (int i = 0; i <= 9; i++) { if ((lCharHigh == NUMERIC[i][0]) && (lCharLow == NUMERIC[i][1])) { intNum = i; break; } else { if ((lCharHigh == 834533329) && (lCharLow == 242870177)) { intNum = 6; } // 834533329 242870177 else { intNum = 1; } // 默认为1 低位为 937393609 937393601 } } return intNum; } /** * 保存图片 * * * @param length * * @return */ public String getValidatecode(int length) { String strNum = ""; for (int i = 1; i <= length; i++) { synchronized (this) { strNum += String.valueOf(getNum(i)); } } return strNum; } /** * Method description * * * @param iag * @param savePath * * @throws FileNotFoundException * @throws IOException */ public void saveJPEG(BufferedImage iag, String savePath) throws FileNotFoundException, IOException { OutputStream jos = new FileOutputStream(savePath); JPEGImageEncoder encoder = JPEGCodec.createJPEGEncoder(jos); JPEGEncodeParam jpegEP = JPEGCodec.getDefaultJPEGEncodeParam(iag); jpegEP.setQuality((float) 1, true); encoder.encode(iag, jpegEP); jos.flush(); jos.close(); }}
恩这样数字是可以识别出来了,可以我要怎么完成提交那块的工作呢?好在Apache已经为我做完了。我用了
HttpClient这样一个模拟Http协议的小工具包。我只要往那个 Add_MSG.asp里面提交就完了。
恩 就这样了,最后结合SAF整成这样了。什么?为什么不用SWT?想过了SWING才是王道o(∩_∩)o...
我先讲讲我是怎么实现站外提交留言到一个网站的程序。
这个网站的留言版大致如下:
我一看这种简单的4位数字验证码,马上就感觉到有戏了。直觉告诉我让电脑来识别这些图片验证码据对简单o(∩_∩)o...
首先我马上在这个页面用右键菜单看源代码
知道验证码获取页面后 你可以直接用 http://www.XXXX.com/imgchk/validatecode.asp 这样去访问你会发现你打开的就是一个验证码图片。
对的其实返回的就是图片文件的2进制流而已。接着先用右键保存一张验证码的图片。因为要开始分析这张图片了,什么用什么工具?PhotoShop????不用就一般的画图工具就可以了。我们要搞清楚的是 这几个数字分别占几个像素就可以了。
可以看出 一个数字5*9 也就是45个像素。恩 这就可以了 另外我们可以看出 默认区域就是白色
(姑且说是白色因为我们肉眼看就是白色)
那么我的程序识别原理就是固定去扫描这45个像素点。看每个点的颜色是不是和默认的颜色一致
一致的话就标记为0 ,不一致就标记为1 。
如一个数子是2 那么我的程序扫描出来的图像就应该是:
011110
100001
000001
000001
000010
000100
001000
010000
100000
111111
如果一个数字是7那么扫描出来的图像就是:
111111
100001
000010
000010
000100
000100
001000
001000
010000
010000
恩,就这么简单呵呵。下面给出图像 扫描的java类 (不好意思,在我会的语言里面除开java就剩sql了)
package com.util;
//~--- JDK imports ------------------------------------------------------------
import com.sun.image.codec.jpeg.JPEGCodec;
import com.sun.image.codec.jpeg.JPEGEncodeParam;
import com.sun.image.codec.jpeg.JPEGImageEncoder;
import java.awt.*;
import java.awt.image.*;
import java.io.*;
import java.io.FileOutputStream;
import java.io.OutputStream;
import java.net.*;
import javax.imageio.*;
import javax.imageio.ImageIO;
/**
* 登陆验证图片转换为数字
*
*
* @version 1.0, 08/04/20
* @author 张健滢
*/
public class ImgIdent {
// 数字字符比特表
private final long[][] NUMERIC = {
{ 512104545, 562436190 }, // '0'
{ 148931080, 136348222 }, // '1'
{ 511971394, 69273663 }, // '2'
{ 511971406, 17045598 }, // '3'
{ 35168914, 586948743 }, // '4'
{ 1065486398, 17045598 }, // '5'
{ 239208494, 830871646 }, // '6'
{ 1065623684, 69239824 }, // '7'
{ 512104542, 562436190 }, // '8'
{ 512104547, 486805660 }
}; // '9'
// 字框高
private int intCharHeight = 10;
// 字框横向间隙
private int intCharSpaceH = 5; // 字框纵向间隙 private int intCharSpaceY = 1; // 字框宽 private int intCharWidth = 5; private int IntImgHeight; private BufferedImage img; private int intBgColor; private int intCharColor; private int intImgWith; private int intMaxX; private int intMaxY; private int intMinX; private int intMinY; // 座标原点 private Point pOrigin; private String strNum; /** * Constructs ... * * * @param img * * @throws IOException */ public ImgIdent(BufferedImage img) throws IOException { this.img = img; init(); } /** * 构造函数 * @param file 本地文件 * @throws IOException */ public ImgIdent(File file) throws IOException { img = ImageIO.read(file); init(); } /** * 构造函数 * @param url 远程文件 * @throws IOException */ public ImgIdent(URL url) throws IOException { img = ImageIO.read(url); init(); } /** * 类初始工作 */ private void init() { // 得到图象的长度和宽度 intImgWith = img.getWidth(); IntImgHeight = img.getHeight(); // 得到图象的背景颜色 intBgColor = img.getRGB(7, 4); // System.out.println(intBgColor); // 初始化图象原点座标 pOrigin = new Point(0, 0); } /** * Method description * */ private void getBaseInfo() { System.out.println(intBgColor + "|" + intCharColor); System.out.println(intMinX + "|" + intMinY + "|" + intMaxX + "|" + intMaxY); } /** * 得到字符的左上右下点座标 * @param intNo int 第n个字符 * @return int[] */ private Point[] getCharRange(int intNo) { // 左上右下点座标 Point pTopLeft = new Point(0, 0); Point pBottomRight = new Point(0, 0); // 左上点 pTopLeft.x = pOrigin.x + intCharWidth * (intNo - 1) + intCharSpaceH * (intNo - 1); pTopLeft.y = pOrigin.y; // 右下点 pBottomRight.x = 1 + pOrigin.x + intCharWidth * intNo + intCharSpaceH * (intNo - 1) - 1; pBottomRight.y = pOrigin.y + intCharHeight - 1; return new Point[] { pTopLeft, pBottomRight }; } /** * 与背景颜色比较返回相应的字符 * @param x int 横座标 * @param y int 纵座标 * @return char 返回字符 */ private char getBit(int x, int y) { int intCurtColor; intCurtColor = img.getRGB(x, y); // System.out.println("[" + x + "," + y + "]" + intCurtColor + "==" + intBgColor + "==>" + (Math.abs(intCurtColor) >7308252));// return (Math.abs(intCurtColor) >= 5689325)// ? '0'// : '1'; return (intCurtColor == intBgColor) ? '0' : '1'; // 5689325 6008535 } /** * 得到第n个字符对应的字符串 * @param intNo int 第n个字符 * @return String 代表字符位的串 */ private String getCharString(int intNo) { // 本字符的左上右下点座标 Point[] p = getCharRange(intNo); Point pTopLeft = p[0]; Point pBottomRight = p[1]; // 换算边界值 int intX1, intY1, intX2, intY2; intX1 = pTopLeft.x; intY1 = pTopLeft.y; intX2 = pBottomRight.x; intY2 = pBottomRight.y;// System.out.println("intX1=" + intX1);// System.out.println("intY1=" + intY1);// System.out.println("intX2=" + intX2);// System.out.println("intY2=" + intY2); // 在边界内循环取象素 int i, j; String strChar = ""; for (i = intY1; i <= intY2; i++) { for (j = intX1; j <= intX2; j++) { System.out.print(getBit(j, i)); strChar = strChar + getBit(j, i); } System.out.println(); } System.out.println(); return strChar; } /** * 得到第n个字符对应数值 * @param intNo int 第n个字符 * @return int 对应数值 */ public int getNum(int intNo) { // 取得位字符串 String strChar = getCharString(intNo); // System.out.println(intNo+"=="+strChar); // 取得串高位串和低位串 String strCharHigh = strChar.substring(0, strChar.length() / 2); String strCharLow = strChar.substring(strChar.length() / 2); // 计算高位和低位值 long lCharHigh = Long.parseLong(strCharHigh, 2); System.out.println(lCharHigh); long lCharLow = Long.parseLong(strCharLow, 2); System.out.println(lCharLow); // 在数字中循环比较 int intNum = '*'; for (int i = 0; i <= 9; i++) { if ((lCharHigh == NUMERIC[i][0]) && (lCharLow == NUMERIC[i][1])) { intNum = i; break; } else { if ((lCharHigh == 834533329) && (lCharLow == 242870177)) { intNum = 6; } // 834533329 242870177 else { intNum = 1; } // 默认为1 低位为 937393609 937393601 } } return intNum; } /** * 保存图片 * * * @param length * * @return */ public String getValidatecode(int length) { String strNum = ""; for (int i = 1; i <= length; i++) { synchronized (this) { strNum += String.valueOf(getNum(i)); } } return strNum; } /** * Method description * * * @param iag * @param savePath * * @throws FileNotFoundException * @throws IOException */ public void saveJPEG(BufferedImage iag, String savePath) throws FileNotFoundException, IOException { OutputStream jos = new FileOutputStream(savePath); JPEGImageEncoder encoder = JPEGCodec.createJPEGEncoder(jos); JPEGEncodeParam jpegEP = JPEGCodec.getDefaultJPEGEncodeParam(iag); jpegEP.setQuality((float) 1, true); encoder.encode(iag, jpegEP); jos.flush(); jos.close(); }}
恩这样数字是可以识别出来了,可以我要怎么完成提交那块的工作呢?好在Apache已经为我做完了。我用了
HttpClient这样一个模拟Http协议的小工具包。我只要往那个 Add_MSG.asp里面提交就完了。
package com.util;
//~--- non-JDK imports --------------------------------------------------------
import org.apache.commons.httpclient.*;
import org.apache.commons.httpclient.methods.GetMethod;
import org.apache.commons.httpclient.methods.PostMethod;
import org.apache.commons.httpclient.params.HttpClientParams;
import org.apache.commons.httpclient.params.HttpMethodParams;
//~--- JDK imports ------------------------------------------------------------
import java.awt.image.BufferedImage;
import java.io.InputStream;
import javax.imageio.ImageIO;
public class MyHttpClient {
/**
* Method description
*
*
* @param title 留言标题
* @param name 留言者
* @param Content 内容
* @param proIP 代理IP
* @param port 代理端口
* @param usePro 是否使用代理
*/
public synchronized void doSomeThing(String title, String name, String Content, String proIP, int port,
boolean usePro) {
// 构造HttpClient的实例
HttpClient httpClient = new HttpClient();
HttpClientParams clientParams = new HttpClientParams();
// 隐藏自己请求相关的信息
clientParams.setParameter("http.useragent", "Mozilla/4.0 (compatible; FIREFOX 9.0; IBM AIX 5)");
// httpClient.getHttpConnectionManager().getParams().setSoTimeout(30 * 1000);
clientParams.setHttpElementCharset("GBK");
HttpState httpState = new HttpState();
httpClient.setParams(clientParams);
httpClient.getParams().setParameter(HttpClientParams.HTTP_CONTENT_CHARSET, "GBK");
httpClient.setState(httpState);
clientParams.setVersion(HttpVersion.HTTP_1_1);
// httpClient.getHostConfiguration().setProxy("148.233.159.58", 3128);
if (usePro) // 使用代理
{
httpClient.getHostConfiguration().setProxy(proIP, port);
}
// 创建GET方法的实例
GetMethod getMethod = new GetMethod("http://www.XXXcom/Guestbook/imgchk/validatecode.asp");
// 使用系统提供的默认的恢复策略
getMethod.getParams().setParameter(HttpMethodParams.RETRY_HANDLER, new DefaultHttpMethodRetryHandler());
try {
// 执行getMethod
int statusCode = httpClient.executeMethod(getMethod);
// System.out.println(statusCode);
if (statusCode != HttpStatus.SC_OK) {
System.err.println("Method failed: " + getMethod.getStatusLine());
} // 读取内容
InputStream inStream = getMethod.getResponseBodyAsStream();
// 处理内容
// System.out.println(new String(responseBody));
BufferedImage iag = ImageIO.read(inStream);
ImgIdent imgIdent = new ImgIdent(iag);
// imgIdent.saveJPEG(iag, "C:/ddd.jpg");
String validate = imgIdent.getValidatecode(4);
System.out.println(validate);
PostMethod method = new PostMethod("http://www.XXX.com/Guestbook/add_msg.asp"); String connect = Content; String Title = title; method.setParameter("subject", Title); method.setParameter("g_name", name); method.setParameter("companyname", ""); method.setParameter("mail", ""); method.setParameter("homepageurl", "http://"); method.setParameter("pic", "p5.gif"); method.setParameter("validatecode", validate); method.setParameter("content", connect);// if (todo) { int code = httpClient.executeMethod(method); // String Stringresponse = new String(method.getResponseBodyAsString().getBytes("8859_1")); // 打印返回的信息 // System.out.println(Stringresponse);// } method.releaseConnection();// System.out.println(iag.getHeight());// System.out.println(iag.getWidth());// //背景 颜色// intBgColor = iag.getRGB(38, 0);// System.out.println("intBgColor=" + intBgColor);////// intBgColor = iag.getRGB(0, 0);// System.out.println("intBgColor=" + intBgColor); } catch (Exception e) { // 发生网络异常 e.printStackTrace(); } finally {} // 释放连接 getMethod.releaseConnection(); } getMethod.releaseConnection(); }}
//~--- non-JDK imports --------------------------------------------------------
import org.apache.commons.httpclient.*;
import org.apache.commons.httpclient.methods.GetMethod;
import org.apache.commons.httpclient.methods.PostMethod;
import org.apache.commons.httpclient.params.HttpClientParams;
import org.apache.commons.httpclient.params.HttpMethodParams;
//~--- JDK imports ------------------------------------------------------------
import java.awt.image.BufferedImage;
import java.io.InputStream;
import javax.imageio.ImageIO;
public class MyHttpClient {
/**
* Method description
*
*
* @param title 留言标题
* @param name 留言者
* @param Content 内容
* @param proIP 代理IP
* @param port 代理端口
* @param usePro 是否使用代理
*/
public synchronized void doSomeThing(String title, String name, String Content, String proIP, int port,
boolean usePro) {
// 构造HttpClient的实例
HttpClient httpClient = new HttpClient();
HttpClientParams clientParams = new HttpClientParams();
// 隐藏自己请求相关的信息
clientParams.setParameter("http.useragent", "Mozilla/4.0 (compatible; FIREFOX 9.0; IBM AIX 5)");
// httpClient.getHttpConnectionManager().getParams().setSoTimeout(30 * 1000);
clientParams.setHttpElementCharset("GBK");
HttpState httpState = new HttpState();
httpClient.setParams(clientParams);
httpClient.getParams().setParameter(HttpClientParams.HTTP_CONTENT_CHARSET, "GBK");
httpClient.setState(httpState);
clientParams.setVersion(HttpVersion.HTTP_1_1);
// httpClient.getHostConfiguration().setProxy("148.233.159.58", 3128);
if (usePro) // 使用代理
{
httpClient.getHostConfiguration().setProxy(proIP, port);
}
// 创建GET方法的实例
GetMethod getMethod = new GetMethod("http://www.XXXcom/Guestbook/imgchk/validatecode.asp");
// 使用系统提供的默认的恢复策略
getMethod.getParams().setParameter(HttpMethodParams.RETRY_HANDLER, new DefaultHttpMethodRetryHandler());
try {
// 执行getMethod
int statusCode = httpClient.executeMethod(getMethod);
// System.out.println(statusCode);
if (statusCode != HttpStatus.SC_OK) {
System.err.println("Method failed: " + getMethod.getStatusLine());
} // 读取内容
InputStream inStream = getMethod.getResponseBodyAsStream();
// 处理内容
// System.out.println(new String(responseBody));
BufferedImage iag = ImageIO.read(inStream);
ImgIdent imgIdent = new ImgIdent(iag);
// imgIdent.saveJPEG(iag, "C:/ddd.jpg");
String validate = imgIdent.getValidatecode(4);
System.out.println(validate);
PostMethod method = new PostMethod("http://www.XXX.com/Guestbook/add_msg.asp"); String connect = Content; String Title = title; method.setParameter("subject", Title); method.setParameter("g_name", name); method.setParameter("companyname", ""); method.setParameter("mail", ""); method.setParameter("homepageurl", "http://"); method.setParameter("pic", "p5.gif"); method.setParameter("validatecode", validate); method.setParameter("content", connect);// if (todo) { int code = httpClient.executeMethod(method); // String Stringresponse = new String(method.getResponseBodyAsString().getBytes("8859_1")); // 打印返回的信息 // System.out.println(Stringresponse);// } method.releaseConnection();// System.out.println(iag.getHeight());// System.out.println(iag.getWidth());// //背景 颜色// intBgColor = iag.getRGB(38, 0);// System.out.println("intBgColor=" + intBgColor);////// intBgColor = iag.getRGB(0, 0);// System.out.println("intBgColor=" + intBgColor); } catch (Exception e) { // 发生网络异常 e.printStackTrace(); } finally {} // 释放连接 getMethod.releaseConnection(); } getMethod.releaseConnection(); }}
恩 就这样了,最后结合SAF整成这样了。什么?为什么不用SWT?想过了SWING才是王道o(∩_∩)o...