验证码的主要实现方法
要解决验证码的问题,我们首先来看看在不同类型的网站上,验证码究竟是如何实现的。
从实现方式上来说,验证码分为“读取式”和“生成式”两种。
“读取式”是指从服务器上指定的目录下随机读取预先制作好的图片文件,将图片文件显示在页面上要求用户识别。粗看起来,这种方式的安全性应该比较好,因为网站制作者可以通过精心制作非常难于自动识别的图片,将自动工具自动识别的风险降到最低,但实际上,这种方式存在一个致命的缺点:容易在页面文件中泄漏图片文件的URL,而恶意用户正好可以利用这一点,通过反复尝试访问使用验证码的页面,获得大部分预先制作好的图片文件URL和需要输入的验证码之间的关系,然后通过该对应关系跳过验证码的验证,使验证码失效。
另外,由于该方法需要预先制作大量的图片文件,前期的工作量比较大,因此,目前已经很少有网站完全采用该方式实现验证码技术。
“生成式”则是指在程序中通过代码的方式,随机生成一个串,并将该串用图形的方式显示在页面上要求用户识别。这种方式由于实现较为方便,因此目前主要的网站均采用该方法实现验证码。当然,“生成式”也有一定的缺点,例如,由于“生成式”一般利用某种网站开发语言提供的图形函数生成图形,每个字符生成的位图是完全相同的,恶意用户可以利用这一点,使用OCR的方式将位图“翻译”成对应的字符串内容。因此,“生成式”一般还会在生成的图片上叠加背景噪音,增加识别的难度。Hotmail和Gmail等网站更是利用变形、改变颜色等方法让验证码的自动识别变得几乎不可能。
总体来说,“生成式”是依靠程序中的代码在运行时动态生成起到验证作用的图片的,但从其具体实现上来看,这种实现方式依赖于具体的编程语言,以及生成图片的格式。
1.1 Xbm图片格式及其动态生成
x-xbitmap格式的图片(以下简称为Xbm格式)由于其特殊性,是一种广泛被用于验证码的图片格式。该图片格式之所以特殊,在于它并不跟gif,jpg等图片格式一样,是一个二进制图片格式,而是采用ASCII码来表示图形——换句话说,它是一个纯文本文件,必须由操作系统对其进行解释才能显示出相应的图片。
以下是一个xbm文件的内容:
#define counter_width 32
#define counter_height 10
static unsigned char counter_bits[] = { 0x
初看起来,这个文件和图形并没有什么关联,但如果我们新建一个文件,将以上内容复制到文件内并将其保存为test.xbm,然后打开IE窗口,并将该文件直接拖拽到它上面后,我们会惊奇地发现,仿佛变魔术一样,显示出来的并不是这个文件的内容,而是一副图片(见图2)。
熟悉C语言的读者肯定一眼就能看出,上面给出的xbm文件的内容完全就是C代码中的一个数组定义。没错,xbm文件就是采用了一个数组来表示一幅图片的。
在上面的文件内容中,#define counter_width 32 定义的是图片的以象素表示的宽度,一般来说,8个象素的宽度可以用来表示一个字符,因此这里的32可以用来表示本图片显示4个字符。
#define counter_height 10定义了图片的高度,表示该图片中每个字符的高度为10象素。而接下来的数组表示的就是图片显示内容的16进制代码了。
一个16进制的字节可以表示为8位二进制数据,xbm文件用二进制的1表示一个黑色的象素,用二进制的0表示一个白色的象素,因此,一个字节可以表示8个象素。以上面的xbm文件为例,一个32×10象素的图片就需要40个字节来表示。xbm文件是按行描述的,对上面的例子来说,每4个字节表示了一行。
因此,如果我们把上述的数组按照4个字节分组进行排列,就会发现字符和数字之间的对应关系。从图3可以看到,由于一个字节能够表示8个象素,而一个图片上的字符宽度也正好是8个象素,因此,对本例来说,按照4个一组的方式排列,很容易得到数组和图片上显示字符的对应关系。
xbm文件可以表示任意的黑白两色图形,而且非常易于生成,因此不少的asp论坛/聊天室的登陆验证码都是采用这样的方法在asp中动态生成的。不过由于攻击者可以利用这种图形格式的处理过程中的漏洞,制造一个超大的图片而导致系统资源耗尽的情况,在Windows XP的SP2以后,就取消了对该图片格式的默认支持,用户必须通过修改注册表才能获得对该图片格式的支持。
1.2 其他图片格式的验证码图形的动态生成
xbm图片文件格式简单,易于生成,但也存在明显的不足——因为图片只能为黑白两色,因此较为容易被自动工具识别。有鉴于此,目前大部分JSP、PHP和ASP.NET网站都利用这些语言本身提供的图形函数在运行时生成图形。
典型的生成图形的过程包括以下几个步骤:
- 生成图形对象;
- 用背景色填充图形对象;
- 随机生成字符串,随机选择前景颜色,利用程序语言的图形库函数以图形方式向图形对象中写入该字符串;
- 向图形对象中增加随机产生的点或者线;
- 输出图片文件头,输出图片文件内容。
下面是一段使用PHP编写可以用来产生验证码的代码:
Header("Content-type: image/PNG");
session_start();
$auth_num="";
session_register('auth_num');
srand((double)microtime()*1000000);
$auth_num_k = md5(rand(0,9999));
$auth_num = substr($auth_num_k,17,5);
$im = imagecreate(58,28);
$black = ImageColorAllocate($im, 0,0,0);
$white = ImageColorAllocate($im, 255,255,255);
$gray = ImageColorAllocate($im, 200,200,200);
imagefill($im,68,30,$gray);
//将四位整数验证码绘入图片
imagestring($im, 5, 10, 8, $auth_num, $black);
for($i=0;$i<50;$i++) //加入干扰象素
{
imagesetpixel($im, rand()%70 , rand()%30 , $black);
}
ImagePNG($im);
ImageDestroy($im);
当然,在具体采用该方法生成验证码时,还可以在代码中通过图形变形等手段让图形变得更难以被自动工具识别。
另外,注意在上面的代码中,我们将随机生成的用于生成验证码图片的实际数据保存到了Session中,这一步对于验证码的实现非常关键,网上广泛流传的一篇用PHP实现验证码的文章将验证码对应的实际数据存放在页面的hidden Field中,从安全性的角度来说,这种方式将导致验证码对应的实际数据在客户端可见,只需要通过简单的页面分析即可获得,这就完全失去了验证码抵抗恶意攻击的作用。
1.3 验证码是否正确的验证过程
产生验证码图片只是验证码技术实现的一个步骤,验证码图片在页面上正确显示后,用户需要识别验证码并提交了相应的内容,验证码技术应该能够判断用户的输入与验证码对应的实际数据是否一致。
在具体的实现中,一般是将验证码对应的实际数据存放在服务端的Session中,在验证用户输入的代码中通过比较用户的输入和验证码对应的实际数据是否一致来判断用户输入的验证码是否正确。
仍然以用PHP实现验证码为例,验证用户输入是否正确的代码如下:
$num=trim($num);
if($auth_num==$num && $num<>"")
{
echo "验证成功";
}
else
{
echo "验证失败";
}
(未完待续)