图形验证码设计目的是利用人脑的不可模拟性来防止机器自动识别.但是一个设计低级的图形
验证码(可以被快速破解)除了增加网络流量以外没有任何意义.网上太多的"生成验证码"
的教程把重点放在如何生成图片上,而实用性却几乎为零.

生成图形本身是零基础技能,任何平台都提供内存图形环境和设备上下文(DC)让你操作,vc中的
CDC,java/.NET中的Graphics,都提供比你需要的还要多的绘图API.可以说介绍这些东西根本没有
必要.(竟然还在某些地方看到图形叠加叫做水印的,图片水印是指可分离的但合成后不可视的图形
透明通道,用于象电子印章之类的加密验证技术).设计一个复杂的难以破解的图形验证码需要了解
常规的可以破解图形验证码的技术种类.

利用session生存期来凭肉眼设别一次后无限次使用同一图形验证码并不算图形验证码的破解.这
只是没有经验的程序员设计上的逻辑BUG.即图形验证码的session存活期是全局的.而不是针对某
次验证过程的.具体过程如下:

客户端请求一个图形验证码.服务器生成一个图形验证码并将验证码的内容放在session中.当客户
端凭肉眼识别通过输入框提交验证码内容后,服务端和session中的内容比较通过.用户其它信息校
验成功后成功登录.
但这时验证码的session还没有过期,客户端用相同的内容还可以为另一次验证使用.所以每个验证
过的验证码的session应该立即销毁.这种逻辑上的BUG可以被没有任何技术经验的人所破解.

真正的对图形识别码进行破解,大多数是对验证码进行切割比对.
假设图形验证码生成的图片上数字是1234

一.切割:首先利用一定算法可以将其切割成最小的四张图片.将四周的空白最大可能地去除.
二.退色:将彩色图片退色成黑白的.用两极法,在0-255中小于128的视为黑色,128到255视为白色.
三.去躁点,将连续黑色范围小于某值,比如小于最小笔划中点的区域做成白色.
四.再进行最小切割.
五.比对,利用已经做好的图形库进行象素比对.因为经过上面的处理,图象都成了黑白两色,以尺寸
最匹配的图片进行比对.先拿图形库中干净的标本图片和没有去躁点的目标比对,看标本图片中每个黑色象
素在目标中是否存在,如果都存在比对通过.目标图片中的黑点在标本图片中没有的应该为躁点.然后
拿去躁后的目标图片的黑色象素去标本图片比对,看是标本图片中是否存在,如果都存在为通过.有可
能去躁不切底,这个过程只能作为参考,通过则为充分条件,不通过不是必要否决条件.

从上面比对过程我们可以看出.比对的最重要的一步是切割,如何能保证目标图片被成功要割成已有
标本图的大小匹配是最关键的技术.

如果你的图片内容生成时本身就是按规则生存有,比如drawString时把一行内容串完整地画出来.
那么间隔都是固定的,字符大小也是固定的.即使每次只画一个字,每个字意隔不同,但只要按最小切
割,也就是把所有的行列中没有有效点的空白去切去,再以一套按最小切割的标本图来比较就很容易了.
字体大小和样式(斜体,下划线,加粗),体型(黑体,宋体)的变化对增加难度不大,只要按不同字号和式
样以及体型多备几套标本库,当然变化越多比对出错可能性越大,但从字体大小和式样上变化不是根本
手段.

如果你的验证码本身只有黑白两色那真正是让破解者太感谢了.复杂的颜色可以让其在退色过程中增
加出错几率.

长条形类似笔画的躁点,在比对时并不起多大作用,因为可以以标本图的象素去找目标图对应象素,躁
点就是多余出来的.但长条形类型笔画的躁点加上不规则间隔对切割起到了巨大的阻碍作用.最最关键
的要点是重叠技术.

两个字之间的部份重叠对于肉眼识别基本上没有障碍,但对于依赖切割比对的机器而言却是致命的克
星.所以保证你的验证码内容中有一些文字内容部份的重叠.如果字数较长,比如8位,其中有两至三处
的重叠,那么基本破解程序就死掉了.有些高级的破解程序利用色差切割,两个字的相交处的不同颜色
来作为切割界限,在这里可以将重叠的字设同色.增加切割难度.

只要无法切割,那么其它方法就无计可施.所以设计一个难以切割的验证码是保证不被破解的最有力的
保证.

文字内容只增加比对时间而已,你用18030个中文字符和用10个数字,比对过程可能会增加1000多倍,对于
机器比对而言难度不大,但很大地加强了标本图库的制作的难度.下面是我用c#做的一个简单例子.复杂的设计用简单来说明,其实抓住最关键的地方就是至少保证有一
次重叠,因为只是例子.真正实用的时候我会做出三次以上重叠.我用不同字号来何证间隔的不规则性.
将原始阿位佰数字和转换后的数字都保存起来,用户可以根据图片内容只输入阿位佰数字或图片上的
内容都可以.注意如果输入的内容中有不好输入的字应该提供一个软键盘之类的输入界面

using System;
using System.Drawing;


namespace testword
{
 /// <summary>
 /// Class1 的摘要说明。
 /// </summary>
 class Class1
 {
  /// <summary>
  /// 应用程序的主入口点。
  /// </summary>
  [STAThread]
  static void Main(string[] args)
  {
   //
   // TODO: 在此处添加代码以启动应用程序
   //
   string CharList = "0123456789";
   int[] size = { 10, 12, 14 };
   string[] fm = { "宋体","楷体_GB2312","黑体"};
 

   DateTime dt = DateTime.Now;
   Random r = new Random();
   int x = r.Next(10000, 100000);

   string tmp = "";
   string src = x.ToString();

   Bitmap bmp = new Bitmap(100, 20);//产生一个宽100高20像素的bmp对象。
   Graphics g = Graphics.FromImage(bmp);
   g.FillRectangle(Brushes.White, 0, 0, 100, 20);
   Console.WriteLine(src);
   int lastSize = 0;
   Color lastColor= Color.Blue;
   bool into = false;
   for (int i = 0; i < 5; i++)
   {
    char ch = CharList[Convert.ToInt32(src[i].ToString())];
    tmp += ch;
    int sz = size[r.Next(0, 3)];
    if (i == 3 && !into) //总共5字,到了第4个还没的重叠的话那第四个要设成14号字
     //保证重叠发生
     sz = 14;
    int cr = r.Next(0, 200);
    int cg = r.Next(0, 200);
    int cb = r.Next(0, 200);
    Color c = Color.FromArgb(cr, cg, cb);
    int sub = 0;
    if (lastSize == 14)
    {//如果上一个字是14号,那么下一字向左入侵保证重叠
     //对大字号重叠的可视性要比小字号强.
     //重叠时最好将当前字符的颜色设为上一字符颜色
     c = lastColor;
     into = true;
     sub = 8;
    }
        g.DrawString(ch.ToString(),
     new Font(new FontFamily(fm[r.Next(0, fm.Length)]), sz),
     new SolidBrush(c),
     new Point(i * 20 - sub, 0));
    //这里因为字号不同,间隔也不同,但每个字的起始点相同,可以修改根据上一个字
    //的大小再调整起始点.
    lastSize = sz;
    lastColor = c;
   }
   bmp.Save("d:/aaa.gif");
   Console.WriteLine(tmp);
           
  }
 }
}

posted on 2007-07-19 15:33  Dayee  阅读(1858)  评论(1编辑  收藏  举报