[原创]红马版验证码实现(中文+变形+噪点)
为了应付越来越多的自动发帖机、恶意攻击等情形,验证码技术在大量的网站上得到使用。我在近期开发一个注册网站的时候,也使用了这一技术。当然,我并不想完完全全自己重新实现,而是参考了网上能够找到的实现,做了若干改进而已。下面谈谈我的实现。
补两张图片:
首先看验证码图片输出页的代码:
<%
@ Page Language
=
"
C#
"
%>
<!
DOCTYPE html PUBLIC
"
-//W3C//DTD XHTML 1.0 Transitional//EN
"
"
http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd
"
>
<
script runat
=
"
server
"
>
protected
void
Page_Load(
object
sender, EventArgs e)
{
VryImgGen gen
=
new
VryImgGen();
string
verifyCode
=
gen.CreateVerifyCode(
5
,
1
);
Session[
"
VerifyCode
"
]
=
verifyCode.ToUpper();
System.Drawing.Bitmap bitmap
=
gen.CreateImage(verifyCode);
System.IO.MemoryStream ms
=
new
System.IO.MemoryStream();
bitmap.Save(ms, System.Drawing.Imaging.ImageFormat.Png);
Response.Clear();
Response.ContentType
=
"
image/Png
"
;
Response.BinaryWrite(ms.GetBuffer());
bitmap.Dispose();
ms.Dispose();
ms.Close();
Response.End();
}
</
script
>
@ Page Language
=
"
C#
"
%>
<!
DOCTYPE html PUBLIC
"
-//W3C//DTD XHTML 1.0 Transitional//EN
"
"
http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd
"
>
<
script runat
=
"
server
"
>
protected
void
Page_Load(
object
sender, EventArgs e)
{
VryImgGen gen
=
new
VryImgGen();
string
verifyCode
=
gen.CreateVerifyCode(
5
,
1
);
Session[
"
VerifyCode
"
]
=
verifyCode.ToUpper();
System.Drawing.Bitmap bitmap
=
gen.CreateImage(verifyCode);
System.IO.MemoryStream ms
=
new
System.IO.MemoryStream();
bitmap.Save(ms, System.Drawing.Imaging.ImageFormat.Png);
Response.Clear();
Response.ContentType
=
"
image/Png
"
;
Response.BinaryWrite(ms.GetBuffer());
bitmap.Dispose();
ms.Dispose();
ms.Close();
Response.End();
}
</
script
>
功能很简单,初始化一个验证码生成对象,生成图片。然后保存到一个MemoryStream里。得到字节流,输出字节流。验证码的数据是保存在Session中的,这是最简单的方法。或者可以加密储存在cookie里,也是可以的。
再来看看验证码生成对象:
using
System;
using
System.Data;
using
System.Configuration;
using
System.Web;
using
System.Web.Security;
using
System.Web.UI;
using
System.Web.UI.WebControls;
using
System.Web.UI.WebControls.WebParts;
using
System.Web.UI.HtmlControls;
using
System.Drawing;
using
System.Text;
///
<summary>
///
Summary description for VryImgGen
///
</summary>
public
partial
class
VryImgGen
{
///
<summary>
///
供验证码生成汉字时选取的汉字集,若为空则按照《GB2312简体中文编码表》编码规则构造汉字
///
</summary>
public
static
string
ChineseChars
=
String.Empty;
///
<summary>
///
英文与数字串
///
</summary>
protected
static
readonly
string
EnglishOrNumChars
=
"
0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
"
;
public
VryImgGen()
{
rnd
=
new
Random(
unchecked
((
int
)DateTime.Now.Ticks));
}
///
<summary>
///
全局随机数生成器
///
</summary>
private
Random rnd;
int
length
=
5
;
///
<summary>
///
验证码长度(默认6个验证码的长度)
///
</summary>
public
int
Length
{
get
{
return
length; }
set
{ length
=
value; }
}
int
fontSize
=
18
;
///
<summary>
///
验证码字体大小(为了显示扭曲效果,默认30像素,可以自行修改)
///
</summary>
public
int
FontSize
{
get
{
return
fontSize; }
set
{ fontSize
=
value; }
}
int
padding
=
4
;
///
<summary>
///
边框补(默认4像素)
///
</summary>
public
int
Padding
{
get
{
return
padding; }
set
{ padding
=
value; }
}
bool
chaos
=
true
;
///
<summary>
///
是否输出燥点(默认输出)
///
</summary>
public
bool
Chaos
{
get
{
return
chaos; }
set
{ chaos
=
value; }
}
Color chaosColor
=
Color.LightGray;
///
<summary>
///
输出燥点的颜色(默认灰色)
///
</summary>
public
Color ChaosColor
{
get
{
return
chaosColor; }
set
{ chaosColor
=
value; }
}
Color backgroundColor
=
Color.White;
///
<summary>
///
自定义背景色(默认白色)
///
</summary>
public
Color BackgroundColor
{
get
{
return
backgroundColor; }
set
{ backgroundColor
=
value; }
}
Color[] colors
=
{ Color.Black, Color.Red, Color.DarkBlue, Color.Green, Color.Orange, Color.Brown, Color.DarkCyan, Color.Purple };
///
<summary>
///
自定义随机颜色数组
///
</summary>
public
Color[] Colors
{
get
{
return
colors; }
set
{ colors
=
value; }
}
string
[] fonts
=
{
"
Arial
"
,
"
Georgia
"
};
///
<summary>
///
自定义字体数组
///
</summary>
public
string
[] Fonts
{
get
{
return
fonts; }
set
{ fonts
=
value; }
}
#region
产生波形滤镜效果
private
const
double
PI
=
3.1415926535897932384626433832795
;
private
const
double
PI2
=
6.283185307179586476925286766559
;
///
<summary>
///
正弦曲线Wave扭曲图片(Edit By 51aspx.com)
///
</summary>
///
<param name="srcBmp">
图片路径
</param>
///
<param name="bXDir">
如果扭曲则选择为True
</param>
///
<param name="nMultValue">
波形的幅度倍数,越大扭曲的程度越高,一般为3
</param>
///
<param name="dPhase">
波形的起始相位,取值区间[0-2*PI)
</param>
///
<returns></returns>
public
System.Drawing.Bitmap TwistImage(Bitmap srcBmp,
bool
bXDir,
double
dMultValue,
double
dPhase)
{
System.Drawing.Bitmap destBmp
=
new
Bitmap(srcBmp.Width, srcBmp.Height);
//
将位图背景填充为白色
System.Drawing.Graphics graph
=
System.Drawing.Graphics.FromImage(destBmp);
graph.FillRectangle(
new
SolidBrush(System.Drawing.Color.White),
0
,
0
, destBmp.Width, destBmp.Height);
graph.Dispose();
double
dBaseAxisLen
=
bXDir
?
(
double
)destBmp.Height : (
double
)destBmp.Width;
for
(
int
i
=
0
; i
<
destBmp.Width; i
++
)
{
for
(
int
j
=
0
; j
<
destBmp.Height; j
++
)
{
double
dx
=
0
;
dx
=
bXDir
?
(PI2
*
(
double
)j)
/
dBaseAxisLen : (PI2
*
(
double
)i)
/
dBaseAxisLen;
dx
+=
dPhase;
double
dy
=
Math.Sin(dx);
//
取得当前点的颜色
int
nOldX
=
0
, nOldY
=
0
;
nOldX
=
bXDir
?
i
+
(
int
)(dy
*
dMultValue) : i;
nOldY
=
bXDir
?
j : j
+
(
int
)(dy
*
dMultValue);
System.Drawing.Color color
=
srcBmp.GetPixel(i, j);
if
(nOldX
>=
0
&&
nOldX
<
destBmp.Width
&&
nOldY
>=
0
&&
nOldY
<
destBmp.Height)
{
destBmp.SetPixel(nOldX, nOldY, color);
}
}
}
return
destBmp;
}
#endregion
///
<summary>
///
生成校验码图片
///
</summary>
///
<param name="code">
验证码
</param>
///
<returns></returns>
public
Bitmap CreateImage(
string
code)
{
int
fSize
=
FontSize;
int
fWidth
=
fSize
+
Padding;
int
imageWidth
=
(
int
)(code.Length
*
fWidth)
+
4
+
Padding
*
2
;
int
imageHeight
=
fSize
*
2
+
Padding
*
2
;
System.Drawing.Bitmap image
=
new
System.Drawing.Bitmap(imageWidth, imageHeight);
Graphics g
=
Graphics.FromImage(image);
g.Clear(BackgroundColor);
//
给背景添加随机生成的燥点
if
(
this
.Chaos)
{
Pen pen
=
new
Pen(ChaosColor,
0
);
int
c
=
Length
*
10
;
for
(
int
i
=
0
; i
<
c; i
++
)
{
int
x
=
rnd.Next(image.Width);
int
y
=
rnd.Next(image.Height);
g.DrawRectangle(pen, x, y,
1
,
1
);
}
}
int
left
=
0
, top
=
0
, top1
=
1
, top2
=
1
;
int
n1
=
(imageHeight
-
FontSize
-
Padding
*
2
);
int
n2
=
n1
/
4
;
top1
=
n2;
top2
=
n2
*
2
;
Font f;
Brush b;
int
cindex, findex;
//
随机字体和颜色的验证码字符
for
(
int
i
=
0
; i
<
code.Length; i
++
)
{
cindex
=
rnd.Next(Colors.Length
-
1
);
findex
=
rnd.Next(Fonts.Length
-
1
);
f
=
new
System.Drawing.Font(Fonts[findex], fSize, System.Drawing.FontStyle.Bold);
b
=
new
System.Drawing.SolidBrush(Colors[cindex]);
if
(i
%
2
==
1
)
{
top
=
top2;
}
else
{
top
=
top1;
}
left
=
i
*
fWidth;
g.DrawString(code.Substring(i,
1
), f, b, left, top);
}
//
画一个边框 边框颜色为Color.Gainsboro
g.DrawRectangle(
new
Pen(Color.Gainsboro,
0
),
0
,
0
, image.Width
-
1
, image.Height
-
1
);
g.Dispose();
//
产生波形(Add By 51aspx.com)
image
=
TwistImage(image,
true
,
8
,
4
);
return
image;
}
///
<summary>
///
生成随机字符码
///
</summary>
///
<param name="codeLen">
字符串长度
</param>
///
<param name="zhCharsCount">
中文字符数
</param>
///
<returns></returns>
public
string
CreateVerifyCode(
int
codeLen,
int
zhCharsCount)
{
char
[] chs
=
new
char
[codeLen];
int
index;
for
(
int
i
=
0
; i
<
zhCharsCount; i
++
)
{
index
=
rnd.Next(
0
, codeLen);
if
(chs[index]
==
'
\0
'
)
chs[index]
=
CreateZhChar();
else
--
i;
}
for
(
int
i
=
0
; i
<
codeLen; i
++
)
{
if
(chs[i]
==
'
\0
'
)
chs[i]
=
CreateEnOrNumChar();
}
return
new
string
(chs,
0
, chs.Length);
}
///
<summary>
///
生成默认长度5的随机字符码
///
</summary>
///
<returns></returns>
public
string
CreateVerifyCode()
{
return
CreateVerifyCode(Length,
0
);
}
///
<summary>
///
生成英文或数字字符
///
</summary>
///
<returns></returns>
protected
char
CreateEnOrNumChar()
{
return
EnglishOrNumChars[rnd.Next(
0
, EnglishOrNumChars.Length)];
}
///
<summary>
///
生成汉字字符
///
</summary>
///
<returns></returns>
protected
char
CreateZhChar()
{
//
若提供了汉字集,查询汉字集选取汉字
if
(ChineseChars.Length
>
0
)
{
return
ChineseChars[rnd.Next(
0
, ChineseChars.Length)];
}
//
若没有提供汉字集,则根据《GB2312简体中文编码表》编码规则构造汉字
else
{
byte
[] bytes
=
new
byte
[
2
];
//
第一个字节值在0xb0, 0xf7之间
bytes[
0
]
=
(
byte
)rnd.Next(
0xb0
,
0xf8
);
//
第二个字节值在0xa1, 0xfe之间
bytes[
1
]
=
(
byte
)rnd.Next(
0xa1
,
0xff
);
//
根据汉字编码的字节数组解码出中文汉字
string
str1
=
Encoding.GetEncoding(
"
gb2312
"
).GetString(bytes);
return
str1[
0
];
}
}
}
System;
using
System.Data;
using
System.Configuration;
using
System.Web;
using
System.Web.Security;
using
System.Web.UI;
using
System.Web.UI.WebControls;
using
System.Web.UI.WebControls.WebParts;
using
System.Web.UI.HtmlControls;
using
System.Drawing;
using
System.Text;
///
<summary>
///
Summary description for VryImgGen
///
</summary>
public
partial
class
VryImgGen
{
///
<summary>
///
供验证码生成汉字时选取的汉字集,若为空则按照《GB2312简体中文编码表》编码规则构造汉字
///
</summary>
public
static
string
ChineseChars
=
String.Empty;
///
<summary>
///
英文与数字串
///
</summary>
protected
static
readonly
string
EnglishOrNumChars
=
"
0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
"
;
public
VryImgGen()
{
rnd
=
new
Random(
unchecked
((
int
)DateTime.Now.Ticks));
}
///
<summary>
///
全局随机数生成器
///
</summary>
private
Random rnd;
int
length
=
5
;
///
<summary>
///
验证码长度(默认6个验证码的长度)
///
</summary>
public
int
Length
{
get
{
return
length; }
set
{ length
=
value; }
}
int
fontSize
=
18
;
///
<summary>
///
验证码字体大小(为了显示扭曲效果,默认30像素,可以自行修改)
///
</summary>
public
int
FontSize
{
get
{
return
fontSize; }
set
{ fontSize
=
value; }
}
int
padding
=
4
;
///
<summary>
///
边框补(默认4像素)
///
</summary>
public
int
Padding
{
get
{
return
padding; }
set
{ padding
=
value; }
}
bool
chaos
=
true
;
///
<summary>
///
是否输出燥点(默认输出)
///
</summary>
public
bool
Chaos
{
get
{
return
chaos; }
set
{ chaos
=
value; }
}
Color chaosColor
=
Color.LightGray;
///
<summary>
///
输出燥点的颜色(默认灰色)
///
</summary>
public
Color ChaosColor
{
get
{
return
chaosColor; }
set
{ chaosColor
=
value; }
}
Color backgroundColor
=
Color.White;
///
<summary>
///
自定义背景色(默认白色)
///
</summary>
public
Color BackgroundColor
{
get
{
return
backgroundColor; }
set
{ backgroundColor
=
value; }
}
Color[] colors
=
{ Color.Black, Color.Red, Color.DarkBlue, Color.Green, Color.Orange, Color.Brown, Color.DarkCyan, Color.Purple };
///
<summary>
///
自定义随机颜色数组
///
</summary>
public
Color[] Colors
{
get
{
return
colors; }
set
{ colors
=
value; }
}
string
[] fonts
=
{
"
Arial
"
,
"
Georgia
"
};
///
<summary>
///
自定义字体数组
///
</summary>
public
string
[] Fonts
{
get
{
return
fonts; }
set
{ fonts
=
value; }
}
#region
产生波形滤镜效果
private
const
double
PI
=
3.1415926535897932384626433832795
;
private
const
double
PI2
=
6.283185307179586476925286766559
;
///
<summary>
///
正弦曲线Wave扭曲图片(Edit By 51aspx.com)
///
</summary>
///
<param name="srcBmp">
图片路径
</param>
///
<param name="bXDir">
如果扭曲则选择为True
</param>
///
<param name="nMultValue">
波形的幅度倍数,越大扭曲的程度越高,一般为3
</param>
///
<param name="dPhase">
波形的起始相位,取值区间[0-2*PI)
</param>
///
<returns></returns>
public
System.Drawing.Bitmap TwistImage(Bitmap srcBmp,
bool
bXDir,
double
dMultValue,
double
dPhase)
{
System.Drawing.Bitmap destBmp
=
new
Bitmap(srcBmp.Width, srcBmp.Height);
//
将位图背景填充为白色
System.Drawing.Graphics graph
=
System.Drawing.Graphics.FromImage(destBmp);
graph.FillRectangle(
new
SolidBrush(System.Drawing.Color.White),
0
,
0
, destBmp.Width, destBmp.Height);
graph.Dispose();
double
dBaseAxisLen
=
bXDir
?
(
double
)destBmp.Height : (
double
)destBmp.Width;
for
(
int
i
=
0
; i
<
destBmp.Width; i
++
)
{
for
(
int
j
=
0
; j
<
destBmp.Height; j
++
)
{
double
dx
=
0
;
dx
=
bXDir
?
(PI2
*
(
double
)j)
/
dBaseAxisLen : (PI2
*
(
double
)i)
/
dBaseAxisLen;
dx
+=
dPhase;
double
dy
=
Math.Sin(dx);
//
取得当前点的颜色
int
nOldX
=
0
, nOldY
=
0
;
nOldX
=
bXDir
?
i
+
(
int
)(dy
*
dMultValue) : i;
nOldY
=
bXDir
?
j : j
+
(
int
)(dy
*
dMultValue);
System.Drawing.Color color
=
srcBmp.GetPixel(i, j);
if
(nOldX
>=
0
&&
nOldX
<
destBmp.Width
&&
nOldY
>=
0
&&
nOldY
<
destBmp.Height)
{
destBmp.SetPixel(nOldX, nOldY, color);
}
}
}
return
destBmp;
}
#endregion
///
<summary>
///
生成校验码图片
///
</summary>
///
<param name="code">
验证码
</param>
///
<returns></returns>
public
Bitmap CreateImage(
string
code)
{
int
fSize
=
FontSize;
int
fWidth
=
fSize
+
Padding;
int
imageWidth
=
(
int
)(code.Length
*
fWidth)
+
4
+
Padding
*
2
;
int
imageHeight
=
fSize
*
2
+
Padding
*
2
;
System.Drawing.Bitmap image
=
new
System.Drawing.Bitmap(imageWidth, imageHeight);
Graphics g
=
Graphics.FromImage(image);
g.Clear(BackgroundColor);
//
给背景添加随机生成的燥点
if
(
this
.Chaos)
{
Pen pen
=
new
Pen(ChaosColor,
0
);
int
c
=
Length
*
10
;
for
(
int
i
=
0
; i
<
c; i
++
)
{
int
x
=
rnd.Next(image.Width);
int
y
=
rnd.Next(image.Height);
g.DrawRectangle(pen, x, y,
1
,
1
);
}
}
int
left
=
0
, top
=
0
, top1
=
1
, top2
=
1
;
int
n1
=
(imageHeight
-
FontSize
-
Padding
*
2
);
int
n2
=
n1
/
4
;
top1
=
n2;
top2
=
n2
*
2
;
Font f;
Brush b;
int
cindex, findex;
//
随机字体和颜色的验证码字符
for
(
int
i
=
0
; i
<
code.Length; i
++
)
{
cindex
=
rnd.Next(Colors.Length
-
1
);
findex
=
rnd.Next(Fonts.Length
-
1
);
f
=
new
System.Drawing.Font(Fonts[findex], fSize, System.Drawing.FontStyle.Bold);
b
=
new
System.Drawing.SolidBrush(Colors[cindex]);
if
(i
%
2
==
1
)
{
top
=
top2;
}
else
{
top
=
top1;
}
left
=
i
*
fWidth;
g.DrawString(code.Substring(i,
1
), f, b, left, top);
}
//
画一个边框 边框颜色为Color.Gainsboro
g.DrawRectangle(
new
Pen(Color.Gainsboro,
0
),
0
,
0
, image.Width
-
1
, image.Height
-
1
);
g.Dispose();
//
产生波形(Add By 51aspx.com)
image
=
TwistImage(image,
true
,
8
,
4
);
return
image;
}
///
<summary>
///
生成随机字符码
///
</summary>
///
<param name="codeLen">
字符串长度
</param>
///
<param name="zhCharsCount">
中文字符数
</param>
///
<returns></returns>
public
string
CreateVerifyCode(
int
codeLen,
int
zhCharsCount)
{
char
[] chs
=
new
char
[codeLen];
int
index;
for
(
int
i
=
0
; i
<
zhCharsCount; i
++
)
{
index
=
rnd.Next(
0
, codeLen);
if
(chs[index]
==
'
\0
'
)
chs[index]
=
CreateZhChar();
else
--
i;
}
for
(
int
i
=
0
; i
<
codeLen; i
++
)
{
if
(chs[i]
==
'
\0
'
)
chs[i]
=
CreateEnOrNumChar();
}
return
new
string
(chs,
0
, chs.Length);
}
///
<summary>
///
生成默认长度5的随机字符码
///
</summary>
///
<returns></returns>
public
string
CreateVerifyCode()
{
return
CreateVerifyCode(Length,
0
);
}
///
<summary>
///
生成英文或数字字符
///
</summary>
///
<returns></returns>
protected
char
CreateEnOrNumChar()
{
return
EnglishOrNumChars[rnd.Next(
0
, EnglishOrNumChars.Length)];
}
///
<summary>
///
生成汉字字符
///
</summary>
///
<returns></returns>
protected
char
CreateZhChar()
{
//
若提供了汉字集,查询汉字集选取汉字
if
(ChineseChars.Length
>
0
)
{
return
ChineseChars[rnd.Next(
0
, ChineseChars.Length)];
}
//
若没有提供汉字集,则根据《GB2312简体中文编码表》编码规则构造汉字
else
{
byte
[] bytes
=
new
byte
[
2
];
//
第一个字节值在0xb0, 0xf7之间
bytes[
0
]
=
(
byte
)rnd.Next(
0xb0
,
0xf8
);
//
第二个字节值在0xa1, 0xfe之间
bytes[
1
]
=
(
byte
)rnd.Next(
0xa1
,
0xff
);
//
根据汉字编码的字节数组解码出中文汉字
string
str1
=
Encoding.GetEncoding(
"
gb2312
"
).GetString(bytes);
return
str1[
0
];
}
}
}
这里面大量使用了
51aspx.com
的代码,在此表示感谢。这里的主要改进在于支持生成中英文混合的验证码。中文的生成有两种方式,一是根据
《GB2312简体中文编码表》
编码规则构造汉字,二是从一个选定的中文字符集合中随即选取汉字。实现很简单,参考函数
protected
char
CreateZhChar(),在此不赘述。
最后说一下验证码的使用,下面是一个例子:
<
img
src
="VerifyCode.aspx"
id
="valiCode"
alt
="验证码"
/>
<
a
title
="刷新验证码"
href
="#"
onclick
="javascript:document.getElementById('valiCode').src='VerifyCode.aspx?id='+Math.random();return false;"
>
看不清,换张图片?
</
a
>
img
src
="VerifyCode.aspx"
id
="valiCode"
alt
="验证码"
/>
<
a
title
="刷新验证码"
href
="#"
onclick
="javascript:document.getElementById('valiCode').src='VerifyCode.aspx?id='+Math.random();return false;"
>
看不清,换张图片?
</
a
>
这里有个小技巧,就是在刷新验证码的使用,验证码的URL后面用了随机参数以欺骗浏览器重新请求。
源代码
2500常用汉字
498常用汉字