一、引言
随着信息化的不断推广,各行业对企事业单位内部、企业间信息化的重视程度也不断提高,重视程度的提高大大促进了各行业无纸化办公的形成。企事业单位流程化管理的规范,节省了大量的人力、物力、资金等资源。信息系统已成为现代企业、政府机构等社会组织提高自身素质和实现组织目标的战略措施。
在信息化发展过程中,人们对信息化中数据信息安全越来越担忧。系统的登录部分对于系统安全而言就象打开保险库的钥匙。把握好这把钥匙可以有效地防止非法用户访问,确保系统安全。系统登录技术涉及密码加密、验证码技术和采用硬件实现算因子认证等技术。
密码加密可以避免密码在传输过程中被侦测窃听,同时可以防止系统管理人员通过数据库知晓所有人员密码,绕开了登录系统。验证码技术现在应用已经非常广泛,比如各大网站系统登录、用户注册、未登录用户评论的发表等都会应用验证码。其原理是根据一定的随机数产生算法生成一串字符串,加入背景、前景及扭曲干扰最终生成验证图片。该图片只能通过肉眼才能识别出其中的验证码字符信息。有效地防止如利用机器人自动批量注册、防止自动批量地提交评论文章以及防止对特定用户利用程序进行暴力破解。系统登录中传统的用户认证方式通常采用人工输入“用户名+密码”的方式。而“用户名+密码”的方式被证明存在众多问题:密码容易被剽窃,用户设置密码通常比较简单,而密码易于共享的特点则可能使一切安全设置流于形式。采用硬件USB Key的身份认证,可以弥补“用户名+密码”认证方式的种种缺陷和不足,消除由于网络内部用户有意或无意造成的安全隐患。
二、加密技术
加密技术通常分为两大类:“对称式(symmetric)”和“非对称式(asymmetric)”。对称式加密就是加密和解密数据时使用相同的密钥和初始化矢量,典型的有DES、 TripleDES和Rijndael算法等。它适用于不需要传递密钥的情况,主要用于本地文档或数据的加密。不对称算法有两个不同的密钥,称为“公钥”和“私钥”,它们两个必需配对使用。公共密钥在网络中传递,用于加密数据,而私有密钥用于解密数据。不对称算法主要有RSA、DSA等,主要用于网络数据的加密。
.Net中常见的加密和编码算法都已经集成在.NET Framework中,实现这些算法的名称空间是:System.Security.Cryptography。由于随着整个框架组件一起共享,密码服务更容易实现了,仅仅需要掌握System.Security.Cryptography名字空间的功能和用于解决特定方案的类。
1. 对称加密
在实际运用中常使用DES和TripleDES对称加密算法。DES主要采用替换和移位的方法,用56位密钥对64位二进制数据块进行加密。TripleDES是在DES的基础上采用三重DES,即用两个56位的密钥K1、K2,发送方用K1加密,K2解密,再使用K1加密。接收方使用K1解密,K2加密,再使用K1解密,其效果相当于密钥长度加倍。DES和TripleDES在.Net中的实现是通过DESCryptoServiceProvider和TripleDESCryptoServiceProvider加密类来实现的。
对称算法是在数据流通过时对它进行加密。因此首先需要建立一个正常的流(例如I/O流)。加密或解密的字符串读入流,算法实例提供一个对象来执行实际数据处理。以DES加密算法为例看看具体实现过程,具体代码如下:
// 根据钥匙加密字符串,返回加密后的字符串
// <param name="stringToEncrypt">要加密的字符串</param>
// <param name="sEncryptionKey">加密的钥匙</param>
public static string Encrypt( string stringToEncrypt, string sEncryptionKey)
{
byte[] key = {};
byte[] IV = {10, 20, 30, 40, 50, 60, 70, 80};
byte[] inputByteArray;
try
{
key = Encoding.UTF8.GetBytes(sEncryptionKey.Substring(0,8));
//实例化DES加密类
DESCryptoServiceProvider des = new DESCryptoServiceProvider();
inputByteArray = Encoding.UTF8.GetBytes(stringToEncrypt);
MemoryStream ms = new MemoryStream();
CryptoStream cs = new CryptoStream(ms, des.CreateEncryptor(key, IV), CryptoStreamMode.Write);
cs.Write(inputByteArray, 0, inputByteArray.Length);
cs.FlushFinalBlock();
return Convert.ToBase64String(ms.ToArray());
}
catch
{ return (string.Empty);}
}
Encrypt加密函数通过指定加密密钥Key和初始化向量(IV)的DESCryptoServiceProvider,传入加密字符串stringToEncrypt,将加密后的字符串作为函数结果返回。如果加密失败返回空字符。解密过程是加密过程的逆过程,方法相同,把CreateEncryptor改为 CreateDecryptor。注意解密过程必须使用加密时所用的同一Key和IV进行解密。解密的核心代码如下:
//用钥匙解密加密的字符串,如果解密失败,返回空字符串
//<param name="stringToDecrypt">要解密的字符串</param>
//<param name="sEncryptionKey">解密所用的钥匙</param>
public static string Decrypt( string stringToDecrypt, string sEncryptionKey)
{
byte[] key = {};
byte[] IV = {10, 20, 30, 40, 50, 60, 70, 80};
byte[] inputByteArray = new byte[stringToDecrypt.Length];
try
{
key = Encoding.UTF8.GetBytes(sEncryptionKey.Substring(0,8));
DESCryptoServiceProvider des = new DESCryptoServiceProvider();
inputByteArray = Convert.FromBase64String(stringToDecrypt);
MemoryStream ms = new MemoryStream();
CryptoStream cs = new CryptoStream(ms, des.CreateDecryptor(key, IV), CryptoStreamMode.Write);
cs.Write(inputByteArray, 0, inputByteArray.Length);
cs.FlushFinalBlock();
Encoding encoding = Encoding.UTF8 ;
return encoding.GetString(ms.ToArray());
}
catch
{ return (string.Empty);}
}
每种算法都有CreateEncryptor和CreateDecryptor两个方法,它们返回实现ICryptoTransform接口的对象。根据以上代码很容易改为TripleDES或者Rijndael算法。
2. 非对称加密
非对称算法主要有RSA、DSA等。通常用于网络数据的加密,信息接收者通过ToXmlString生成公钥和私有,通过网络或者其他方式把公钥传递给信息发送者。信息发送者通过FromXmlString导入公钥,并对字符流进行加密。接收方接收到数据后用自己的私钥解密数据。这两种加密算法分别通过RSACryptoServiceProvider和DSACryptoServiceProvider类来实现。以RSA为例,具体代码如下:
//待加密的明文
string originText="I am Dean Zhang";
RSACryptoServiceProvider rsaReceive =new RSACryptoServiceProvider();
RSACryptoServiceProvider rsaSend = new RSACryptoServiceProvider();
//接收方先生成公钥,并将此公钥公开给信息发送者,参数false 表示只生成公钥, 如果为true, 则//生成私钥
string publicKey = rsaReceive.ToXmlString(false);
// 产生私钥
string privatekey = rsaReceive.ToXmlString(true);
//发送方接收公钥, 并用此公钥加密数据 rsaSend.FromXmlString(publicKey);
//发送方执行加密程序,Encrypt第二个参数指示是否使用OAEP,如果为 true,则使用 OAEP 填充,//如果为 false,则使用 PKCS#1 1.5版填充, 解密时跟加密时的选择相同
byte[] cryp = rsaSend.Encrypt(Encoding.UTF8.GetBytes(originText),false);
//接收方用自己的私钥解密
byte[] b_OriginText = rsaReceive.Decrypt(cryp, false);
3. 哈希(Hash)值
哈希算法将任意长度的二进制值映射为固定长度的较小二进制值,这个小的二进制值称为哈希值。哈希值是一段数据唯一且极其紧凑的数值表示形式。如果散列一段明文而且哪怕只更改该段落的一个字母,随后的哈希都将产生不同的值。要找到散列为同一个值的两个不同的输入,在计算上是不可能的,所以数据的哈希值可以检验数据的完整性。在实际应用中常见的就是MD5加密算法,密码通过MD5算法转换为固定长度的Hash值并存储。验证过程为把用户输入的密码经过MD5算法和存储的HASH进行比较,如果一致表明通过。MD5算法示例代码如下:
public static string getMD5Str(string ConvertString)
{
string md5Str = "";
MD5CryptoServiceProvider md5 = new MD5CryptoServiceProvider();
string md5Str = BitConverter.ToString(md5.ComputeHash(UTF8Encoding.Default.GetBytes(ConvertString)), 4, 8);
md5Str = md5Str.Replace("-", "");
return md5Str;
}
其实严格地来说,这种方式不是一种对数据进行加密的算法,是一种签名算法。它不能对加密的密码进行解密,但因为不同的密码不可能有相同的Hash值,可以通过这个特性对密码进行验证,其缺点是不能还原(或者说是解密)读取用户密码信息。
三、验证码技术
验证码的实现流程为:服务器端采用随机数产生算法生成验证码字符串,保存在服务器端(如内存中)并写入图片,图片里加上一些干扰像素(防止OCR),把该图片信息发送给客户端(可能是浏览器或者C/S客户端),客户端输入验证码图片上字符信息,然后提交给服务器端,提交的字符和服务器端保存的该字符进行比较。一致就继续,否则返回提示。攻击者编写的robot程序,很难识别验证码字符,顺利地完成自动注册、登录、批量提交等,而用户可以识别填写,所以这就实现了阻挡攻击的作用。防止图片的字符被自动识别,就要看图片上的干扰强度了。
普通的验证码字符大多是“随机数字+随机英文字母”,并且只是增加了背景和前景噪音干扰。验证作用弱,很多程序可以通过OCR识别出验证图片的验证码信息。通过随机生成汉字字符串,把生成的图像进行波形扭曲,有效地防止OCR,增强抗干扰。
首先通过函数RndNum(int VcodeNum)产生自定长度的汉字,输入参数为生成汉字长度。如果需要加入生成带字母和数字的随机字符串,在str变量里加入字母和数字就可以了。具体代码如下:
// 随机产生指定长度的汉字
public String RndNum(int VcodeNum)
{
string str = "的一是在不了有和…红细引听该铁价严";//常用汉字
string code = "";
Random rd = new Random();
int i;
for (i = 0; i < VcodeNum; i++)
{
code += str.Substring(rd.Next(0, str.Length), 1);
}
return code;
}
然后根据随机汉字字符串生成验证图片,创建一个BMP位图,从枚举类型Colors和Fonts里随机生成Brush和Font,用该Brush和Font绘制验证信息。并在其上增加背景噪音线、前景噪音点和边框线。具体代码如下:
//生成图片(增加背景噪音线、前景噪音点和边框线)
//<param name="checkCode">随机字符串</param>
public byte[] CreateVerifyCodeImage(string checkCode)
{
if (checkCode.Trim() == "" || checkCode == null)
return null;
int Padding = 2;
int fontSize = 28;
Color[] Colors = { Color.Black, Color.Red, Color.DarkBlue, Color.Green, Color.Orange, Color.Brown, Color.DarkCyan, Color.Purple };
string[] Fonts = { "Arial", "Georgia", "宋体", "黑体", "幼圆" };
int fontWidth = fontSize + Padding;
int imageWidth = (int)(checkCode.Length * (fontWidth)*1.4) + Padding * 2;
int imageHeight = 40;
//创建BMP位图
Bitmap image = new Bitmap(imageWidth, imageHeight);
Graphics g = Graphics.FromImage(image);
try
{
g.Clear(Color.White);
Random rand = new Random();
// 画图片的背景噪音线
int c = 3 * 20;
for (int i = 0; i < c; i++)
{
int x1 = rand.Next(image.Width);
int x2 = rand.Next(image.Width);
int y1 = rand.Next(image.Height);
int y2 = rand.Next(image.Height);
g.DrawLine(new Pen(Color.Silver), x1, y1, x2, y2);
}
int left = 0, top = 0, top1 = 1, top2 = 1;
int n1 = (imageHeight - fontSize - Padding * 2*2);
int n2 = n1 / 4;
top1 = n2;
top2 = n2 * 2;
Font f;
Brush b;
int cindex, findex;
//随机字体和颜色的验证码字符
for (int i = 0; i < checkCode.Length; i++)
{
cindex = rand.Next(Colors.Length - 1);
findex = rand.Next(Fonts.Length - 1);
f = new Font(Fonts[findex], fontSize, FontStyle.Bold);
b = new SolidBrush(Colors[cindex]);
if (i % 2 == 1)
{ top = top2; }
else
{ top = top1; }
left = (int)(i *fontWidth*1.4) ;
g.DrawString(checkCode.Substring(i, 1), f, b, left, top);
//画图片的前景噪音点Orange
g.DrawRectangle(new Pen(Color.Orange), 0, 0, image.Width - 1, image.Height - 1);
}
//画一个边框边框颜色为Color.Gainsboro
g.DrawRectangle(new Pen(Color.Gainsboro, 0), 0, 0, image.Width - 1, image.Height - 1);
g.Dispose();
//产生波形
image = TwistImage(image, true, 6, 4);
MemoryStream ms = new MemoryStream();
image.Save(ms, Imaging.ImageFormat.Gif);
return ms.ToArray();
}
catch
{
g.Dispose();
image.Dispose();
return null;
}
}
生成的图片其实就是验证码图片,应该说已经达到了图片验证码效果了。通过OCR识别技术还是能自动分析出图片中验证码字符串的信息。为了加强图片的抗干扰能力,需要对图片进行波形扭曲,生成的验证码图片如图1所示。波形扭曲实现代码如下:
// 正弦曲线Wave扭曲图片
// <param name="srcBMP">图片</param>
// <param name="bXDir">如果扭曲则选择为True</param>
//<param name="nMultValue">波形的幅度倍数,越大扭曲的程度越高,一般为5</param>
// <param name="dPhase">波形的起始相位,取值区间[0-2*PI)</param>
protected Bitmap TwistImage(Bitmap srcBMP, bool bXDir, double dMultValue, double dPhase)
{
Bitmap destBMP = new Bitmap(srcBMP.Width, srcBMP.Height);
// 将位图背景填充为白色
Graphics graph = Graphics.FromImage(destBMP);
graph.FillRectangle(new SolidBrush(Color.Silver), 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);
Color color = srcBMP.GetPixel(i, j);
if (nOldX >= 0 && nOldX < destBMP.Width
&& nOldY >= 0 && nOldY < destBMP.Height)
{
destBMP.SetPixel(nOldX, nOldY, color);
}
}
}
return destBMP;
}
图1 Web方式系统登录界面
|