安全第一:.NET加密技术指南 (1)
http://www.sj265.com/Html/2005122994819-1.Html
Web服务以不容置疑的态势迅速发展,促使许多单位开始考虑.NET之类的开发平台。但是,出于对安全问题的担心,一些单位总是对采用新技术心存顾虑。好在有许多成熟的安全和网络技术,例如虚拟私有网络(VPN)和防火墙等,能够极大地提高Web服务应用的安全和性能,让开发者拥有选择安全技术的自由,而不是非得使用尚在发展之中的XML安全技术不可。
虽然安全是信息系统的首要问题,但有关.NET安全和加密工具的范例却少之又少。看看大多数.NET书籍的目录,找不到任何有关安全的题目,更不用说关于密码系统的探讨了。
有鉴于此,本文将介绍如何在VB开发中运用.NET的加密和密钥生成类,提供一个可用来加密和解密文件的工具Cryption。有了这个工具,你就可以在硬盘上保存各种机密文件,例如所有的密码/用户名字信息、收支文件、以及其他想要保密的信息,还可以加密那些通过Internet发送的文件。加密技术的用途非常广泛,你可以进一步定制本文提供的工具满足某些特殊需要,例如增加批处理能力等。
一、两类重要的安全威胁
攻击和泄密是计算机面临的两大安全威胁。攻击可能来自病毒,例如它会删除文件、降低机器运行速度或引发其它安全问题。相比之下,泄密往往要隐蔽得多,它侵害的是你的隐私:未经授权访问硬盘文件,截取通过Internet发送的邮件,等等。泄密还可能伴随着攻击,例如修改机密文件等。
针对泄密的最佳防范措施就是加密。有效的加密不仅杜绝了泄密,而且还防范了由泄密引发的攻击。加密技术有时还用于通信过程中的身份验证--如果某个用户知道密码,那么他应该就是那个拥有这一身份的人。
然而必须说明的是,没有一种防范泄密的安全技术是绝对坚固的,因为密码有可能被未经授权的人获得。
二、使用.NET加密功能的前提
首先,要想使用.NET的安全功能,就必须用Imports语句引入加密用的包。试验本文涉及的任何代码之前,请在VB代码窗口的顶部加入下列Imports语句:
Imports System.IO
Imports System.Text
Imports System.Security.Cryptography
第二,美国政府过去限制某些加密技术出口。虽然这些限制不再有效,.NET框架在Windows的出口版本中禁用了“高级”加密技术。如果你的Windows不带高级加密能力,可以从微软网站下载更新包:对于Windows 2000,安装Service Pack 2包含的High Encryption Pack;对于NT,安装Service Pack 6a。对于Windows ME、95、98的用户,IE 5.5也包含了High Encryption Pack。
三、加密/解密工具概况
本文提供的工具可用来加密和解密文件,如果你急着给一些文件加密,只需直接启动本文后面提供的工具即可,图一就是Cryption工具的运行界面。
图一
这个工具提供了一个用来输入文件名字的文本框和一个输入密钥的文本框,通过便捷的用户界面提供加密、解密和密钥生成功能。在图一中,上方的文本框用来输入待加密/解密文件的名字;下面的文本框用来输入8个字符的密码。执行加密操作之后将生成一个新的文件,这个经过加密的文件和原始文件在同一目录下,文件名字也和原始文件的一样,但加上了“xx”后缀,例如,假设原始文件是MyFile.txt,则加密得到的文件是MyFilexx.txt。
加密好之后,原始文件不一定非删除不可,但一般来说最好删除,因为加密的根本目的就是为了隐藏原始文件的数据。如果要从加密后的文件恢复出原始文件,在上面的文本框中输入MyFilexx.txt,然后提供密码,Cryption工具将创建一个与原始文件一样的MyFile.txt文件。也就是说,Cryption把文件名字后面的“xx”看作是要求解密密文的标志。
注意:加密文件之后如果忘记了用来加密该文件的密码,再想恢复出原始文件就不可能了。当然,这与密码本身的复杂程度有关,要想保证文件的安全,最好采用较复杂的密码,例如混合运用字母、数字和特殊字符(如“$”符号等)。
.NET提供的加密技术不止一种,不过本文讨论的主要是对称加密。对称加密也称为私有密钥加密,它的特点是加密和解密用的是同一个密钥(实际上是同一种算法),解密方和加密方都有责任保障密码的安全(对于公用密钥、不对称加密,密钥一共有两个,其中一个密钥是公开的,这是当前公认最有效的加密技术,但就速度而言要比对称加密算法慢不少)。
在正式利用.NET加密类加密文件之前,首先必须从用户提供的密码生成一个密钥。密钥可以利用Hash函数生成,Hash函数把用户的密码字符串转换成一组类似随机数序列的、无意义的数据,这组数据可作为密钥使用,在加密过程中对原始数据进行唯一性变形处理。
例如,用密钥加密数据的一种办法是把原始数据的ASCII码加上密钥的ASCII码:
密钥:ab = ASCII: 97, 98
数据:merry = ASCII: 109, 101, 114, 114, 121
把这组数据的ASCII码加上密钥的ASCII码(必要时重复使用密钥),得到的加密结果是:
97 98 97 98 97
+109 +101 +114 +114 +121
206 199 211 212 218
对于同样的数据,Hash算法总是生成同样的结果(这就是说,对于同一个密码,同一Hash算法总是生成相同的bit序列)。实际上,在本文提供的代码中,利用.NET的SHA1CryptoServiceProvider类的ComputeHash方法可以验证这一点,例如,对于同一个输入参数morph,任何时候该方法总是返回下面的结果:124,230,93,253,197,206,136,72。因此,如果有人知道密码以及生成密钥的算法,他也可以轻松地推算出密钥。
四、执行加密/解密
.NET加密技术要求密钥有确定的长度,例如,DES(Data Encryption Standard)函数要求密钥的长度是64位,Rijndael则要求128、192或256位长度的密钥。密钥越长,加密强度越高。对于DES之外的加密算法,查询LegalKeySizes属性即可得到它允许的密钥长度,包括MinSize(支持的最小密钥长度)、MaxSize(最大密钥长度)、SkipSize(增量)。SkipSize表示密钥最大长度和最小长度之间可用长度的间隔,例如,Rijndael算法的SkipSize值是64位。
利用下面的代码可以得到密钥的长度信息:
' 创建DES加密对象
Dim des As New DESCryptoServiceProvider()
Dim fd() As KeySizes
fd = des.LegalKeySizes() 'tells us the size(s), in bits
MsgBox("加密类型=" & des.ToString() & Chr(13) & "minsize = " & fd(0).MinSize & Chr(13) & _
"maxsize = " & fd(0).MaxSize & Chr(13) & "skipsize = " & fd(0).SkipSize)
运行上面的代码,得到的结果是64、64、0(图二)。如果把加密对象的声明改成TripleDESCryptoServiceProvider(),得到的结果是128、192、64(图三)。
图二
图三
说明:DES算法要求输入一个8字节的密码,但实际使用的密钥只有56位(7个字节),每一个字节的最后一位不用(它作为校验位使用,但不用于实际的加密过程)。
下面的代码开始生成本文示例程序的密钥:
Public Class Form1
Inherits System.Windows.Forms.Form
' 保存密钥的8字节的数组
Private TheKey(7) As Byte
' 在向量中放入一些随机数据
Private Vector() As Byte = {&H12, &H44, &H16, &HEE, &H88, &H15, &HDD, &H41}
首先,代码定义了保存密钥和初始向量(请参见稍后的详细说明)的两个变量。向量的初值这里用随机数据填充,当然,通过密码和Hash算法也可以获得向量的初值。下面的过程从用户输入的密码创建出密钥:
Sub CreateKey(ByVal strKey As String)
' 保存密钥的字节数组
Dim arrByte(7) As Byte
Dim AscEncod As New ASCIIEncoding()
Dim i As Integer = 0
AscEncod.GetBytes(strKey, i, strKey.Length, arrByte, i)
' 获得密码的Hash值
Dim hashSha As New SHA1CryptoServiceProvider()
Dim arrHash() As Byte = hashSha.ComputeHash(arrByte)
' 将Hash值保存到密钥
For i = 0 To 7
TheKey(i) = arrHash(i)
Next i
End Sub
用户的密码(strKey)传入到CreateKey过程,分解成一组ASCII值保存到一个字节数组。把这个字节数组传递给SHA1CryptoServiceProvider类的ComputeHash方法,返回一个Hash值。把这个Hash值保存到TheKey数组,供以后的加密/解密过程使用(注意SHA1CryptoServiceProvider实际能够支持160位,但本文示例程序只用到64位)。
那么,初始向量究竟起什么作用呢?这个字节数组有8个元素,就象密钥一样,但向量和密钥的作用是不同的,向量用来避免DES之类的算法一个特有的问题。在DES之类的算法中,原始数据被分成8字节一块然后分别处理。DES在加密一块数据时,要用到前一块数据的模式,也就是说,如果改动了原始数据中第一块的某个字符,所有后继的块的内容都将随之改变,从而避免了一系列相连接的块中出现重复块的问题。
例如,假设你一时高兴,发了一个邮件,内容只有几个重复的单词“Melanie! Melanie! Melanie! Melanie!”,在密钥和块序列中前一块的共同作用下,加密之后的密文不会出现重复现象。然而,进一步考虑这个加密过程可以发现,如果用同一个密钥加密多个邮件,且邮件开头的问候语都相同,则邮件开头的一部分很容易受到攻击。由于这个原因,我们用初始向量来模拟前一个块。
本文加密/解密工具中的下面这段代码示范了如何加密文件:
Sub Encrypt(ByVal inName As String , ByVal outName As String )
Try
' 创建缓冲区
Dim storage(4096) As Byte
' 已经写入的字节数量
Dim totalBytesWritten As Long = 8
' 每次写入的字节数量
Dim packageSize As Integer
' 声明文件流
Dim fin As New FileStream(inName, FileMode.Open, FileAccess.Read)
Dim fout As New FileStream(outName, FileMode.OpenOrCreate, FileAccess.Write)
fout.SetLength(0)
' 源文件的大小
Dim totalFileLength As Long = fin.Length
' 创建加密对象
Dim des As New DESCryptoServiceProvider()
Dim crStream As New CryptoStream(fout, _
des.CreateEncryptor(TheKey, Vector), _
CryptoStreamMode.Write)
' 输出加密后的文件
While totalBytesWritten < totalFileLength
packageSize = fin.Read(storage, 0, 4096)
crStream.Write(storage, 0, packageSize)
totalBytesWritten = Convert.ToInt32(totalBytesWritten + _
packageSize / des.BlockSize * des.BlockSize)
End While
crStream.Close()
Catch e As Exception
MsgBox(e.Message)
End Try
End Sub
注意这段代码创建了三个文件流:fin,表示明文形式的原始文件;fout,加密结果文件;crStream,加密流,用来把DES加密的结果转入输出文件fout。增加一个crStream流的好处是不必把结果保存到临时文件或缓冲区。
加密过程与解密过程的唯一重要区别是,执行解密时,我们将使用DESCryptoServiceProvider对象的另一个方法CreateDecryptor,除此之外,两者其余的处理步骤(包括参数,文件流,等等)基本相同。
五、防止破解
黑客和密码专家破解加密文件的办法主要有两个,第一是搜索密文是否有重复现象,第二是用暴力破解法获得密钥。首先我们考虑一下初始向量如何防止重复现象,然后再探讨一下防止暴力破解的关键问题。
破解密文的第一种方式是搜索样本--特别是重复的样本。人们在写信的时候总是喜欢用同样的文字开头,例如“亲爱的XXX”、“Dear Sir”等,如果多个邮件的开头文字相同且通过同一密钥加密,则每个密文信件的开头也相同。假设Antonio写给Melanie的所有加密信件都有相同的问候语“@4^F (2$@Fx”,解密者就会首先检查开头的几个单词是不是“Dear Melanie”。解密机密文件的一个重要步骤就是猜测文件中应当会出现的几个单词,所以我们不应该给解密者提供这种方便。在本文的示例中,初始向量的内容被附加到文件的开头,从而防止了出现重复现象。只有信件的开头才容易受到此类攻击。
计算机的运算速度和精度要远远超过人,特别擅长处理一些重复的任务,例如尝试每一种可能的密钥组合最终破解密钥。DES加密算法本身是不安全的,这种加密算法早在70年代就已经公之于众。而且,破解者如果想要让搜索密钥的过程自动化,同样可以方便地运用.NET的DESCryptoServiceProvider类。
对于一个128位、结合运用密钥/初始向量的加密方案,计算机尝试每一种可能的密钥组合要花多少时间?专家们的看法并不一致,有人认为需要数月,也有人认为装有专用硬件的价值6位数的计算机每秒能够验证数十亿个密钥,破解DES密文只需数小时。如果你的机密值得花数月时间去破解,那么最好改用TripleDES或其他加密算法。从TripleDES的名字也可以猜出,这种加密方式采用三重数据加密标准算法,所以密钥的长度是192位,而不是64位的DES密钥。记住,在其他条件相同的情况下,密钥越长,安全程度越高。
结束语:现在你已经了解了.NET DES加密算法的使用过程,接下去可以研究.NET的其他安全功能,包括极具吸引力的公用密钥加密方案。虽然公用密钥加密方案执行起来速度慢一些,但加密效果一般要比TripleDES好。本人没有什么机密值得运用DES之外的算法,不过你的要求可能有所不同。