存档

文章标签 ‘验证码’

图形验证码的破解与设计【转】

2009年9月10日 没有评论

图形验证码设计目的是利用人脑的不可模拟性来防止机器自动识别.但是一个设计低级的图形验证码(可以被快速破解)除了增加网络流量以外没有任何意义.网上太多的”生成验证码”的教程把重点放在如何生成图片上,而实用性却几乎为零.生成图形本身是零基础技能,任何平台都提供内存图形环境和设备上下文(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#做的一个简单例子.复杂的设计用简单来说明,其实抓住最关键的地方就是至少保证有一次重叠,因为只是例子.真正实用的时候我会做出三次以上重叠.我用不同字号来何证间隔的不规则性.将原始阿位佰数字和转换后的数字都保存起来,用户可以根据图片内容只输入阿位佰数字或图片上的内容都可以.注意如果输入的内容中有不好输入的字应该提供一个软键盘之类的输入界面.
string[] CharList = { “零壹贰叁肆伍陆柒捌玖”, “○一二三四五六七八九” };
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);
Graphics g = Graphics.FromImage(bmp);
g.FillRectangle(Brushes.White, 0, 0, 100, 20);
Console.WriteLine(src);
int lastSize = 0;
Color lastColor;
bool into = false;
for (int i = 0; i < 5; i++)
{
char ch = CharList[r.Next(0, 2)][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);

TimeSpan ts = DateTime.Now – dt;
Console.WriteLine(ts.Milliseconds);

这样的过程在我的台式机上一般在15毫秒左右,在服务器上应该更快.
效果:
肉眼很容易识别,但机器却很难切割
其实这其中还有更多的技巧,但从总体而言就是保证不能被机器正确地切割.没有切割,那么就不能处理比对.
(当然利用银河机进行组合整体比对理论上也是可以的).在防切割上的重点设计才是真正的设计目标.

php产生验证码完整案例

2009年8月28日 没有评论

今天继续开发zen cart项目,加上了验证码的功能,考虑到需要较高的安全性,自己手工写了个小程序,功能还算全面,自动检测背景和字体,并随机选取背景图片中的一块范围,随机使用字体,显示验证字符串时随机显示字体大小,字符间距,字符颜色等。以下是程序代码。
validimg.php文件

<?php
/**
* Class for Validate image
* @author  zcs
* @version 1.0-20090828
*/

session_start();
class validimg
{
//背景图片目录
var $backgroundpath = ‘validbg’;
//生成验证码宽度
var $width =’80′;
//生成验证码高度
var $height =’25′;
//背景
var $background;
//验证文本
var $text=’abcd’;
//字体目录
var $fontpath = ‘validbg’;
//字体
var $font=’simhei.ttf’;
//字体宽度
var $font_width = ’20′;

function validimg($text)
{
$this->text = $text;
//随机选取一个背景文件
$bgdir =  @dir($this->backgroundpath);
while(false !== ($image = $bgdir ->read()))
{
if($image != ‘.’ && $image != ‘..’ && $this->checktype($image) != false)
{
$backgroundarr[] = $image;

}
}
$bgdir->close();
//随机选取一个字体文件
$fonts =  @dir($this->fontpath);
while(false !== ($font = $fonts ->read()))
{
if($font != ‘.’ && $font != ‘..’ && $this->checktype($font,’FONT’) != false)
{
$fontsarr[] = $font;
}
}
$fonts->close();
$this->font = $fontsarr[array_rand($fontsarr,1)];
$this->background = $backgroundarr[array_rand($backgroundarr,1)];

$this->output();
}

//创建背景图像handdle
function createbackground()
{
switch ($this->checktype($this->background))
{
case ‘jpg’:
$bghanddle = @imagecreatefromjpeg( $this->backgroundpath.’/’.$this->background);
break;
case ‘gif’:
$bghanddle = @imagecreatefromgif( $this->backgroundpath.’/’.$this->background);
break;
case ‘png’:
$bghanddle = @imagecreatefrompng( $this->backgroundpath.’/’.$this->background);
break;
default:
}
return $bghanddle;
}
//检查文件类型
function checktype( $image,$type = ‘IMAGE’)
{
$ext = substr( $image, strrpos($image,’.')+1);
if($type == ‘IMAGE’)
{
if ($ext == ‘jpg’ || $ext ==’gif’ || $ext ==’png’)
return $ext;
else return false;
}else if($type == ‘FONT’)
{
if ($ext == ‘ttf’)
return $ext;
else return false;
}
}
//输出
function output()
{
header(“content-type:image/png;”);
//生成图像
$img = @imagecreatetruecolor( $this->width,$this->height);
$bghanddle = $this->createbackground();
//从背景图像随机位置载入一块作为背景
if($bghanddle)
{
$randx=rand(0,(imagesx($bghanddle) – $this->width));
$randy=rand(0,(imagesy($bghanddle) – $this->height));
}
imagecopy($img,$bghanddle,0,0,$randx,$randy,$this->width,$this->height);
//随机选择角度 字体大小 坐标输出文字
for($i=0;$i<strlen($this->text);$i++)
{
$angle = rand(-30,30);
$fontsize = rand(15,20);
$x = rand($this->font_width*$i,$this->font_width*$i+10);
$color = imagecolorallocate($img, rand(0,255), rand(0,255), rand(0,255));
imagettftext($img,$fontsize,$angle,$x,20,$color,$this->fontpath.’/’.$this->font,substr($this->text,$i,1));
}
imagepng($img);
//释放资源
imagedestroy($img);
imagedestroy($bghanddle);
}
}

new validimg($_SESSION['valid']);
?>

测试文件validtest.php

<?php
/**
* 验证码测试
* @author  zcs
*/
session_start();
$_SESSION['valid']= randstr();
echo $_SESSION['valid'];

//随机生成字符串

function randstr($num=4)
{
$chars = ‘ABDEFGHJKLMNPQRSTVWXYabdefghijkmnpqrstvwxy23456789′;
$randstr=”;
for($i=0;$i<$num;$i++)
{
$randstr.=substr($chars,rand(0,strlen($chars)),1);
}
return $randstr;
}
?>
<img src=”validimg.php” />

Demo http://anfirst.cn/case/validimg/

代码包下载:validimg