在信息安全的实际应用中,需要对计算机中大容量文件或数据进行加解密、数字签名以及完整性验证等工作。在VC等平台下实现的相关软件有很多,但是它们在处理大容量文件或数据时的执行效率成为应用的瓶颈。如何提高加解密程序执行的效率是本文研究和解决的重点。利用.NET基础类库在system.security.cryptography命名空间下实现的诸多加密服务提供类很好地达到了此目的。
一、简介
常见的加解密、完整性验证以及数字签名算法都已经在.NET Framework中得到了实现,为编码提供了极大的便利性,实现这些算法的命名空间是system.security.cryptography。此命名空间按如下方式组织:
1.对称加密
与公钥算法相比,对称加密算法非常快,特别适用于对较大的数据流执行加密。.NET Framework提供以下实现对称加密算法的类: ①DESCryptoServiceProvider;②RC2CryptoServiceProvider;③RijndaelManaged;④TripleDESCryptoServiceProvider。
2.公钥加密和数字签名
公钥加密算法也称为非对称算法,使用一个必须对未经授权的用户保密的私钥和一个可以对任何人公开的公钥。用公钥加密的数据只能用私钥解密,而用私钥签名的数据只能用公钥验证。公钥可以被任何人使用,该密钥用于加密要发送到私钥持有者的数据。两个密钥对于通信会话都是唯一的。.NET Framework提供以下实现公钥加密算法的类:①DSACryptoServiceProvider;②RSACryptoServiceProvider。
3.消息摘要
消息摘要也称Hash算法,能将任意长度的二进制值映射为固定长度的短二进制值(Hash值)。如果对一段明文哪怕只更改其中的一个字母,随后的Hash值都将产生巨大的变化。要找到Hash值相同的两个不同的输入,在计算上是不可能的,所以数据的Hash值可以检验数据的完整性。.NET Framework提供以下实现数据完整性算法的类:①HMACSHA1;②MACTripleDES;③MD5CryptoServiceProvider;④SHA1Managed、SHA256Managed、SHA384Managed、SHA512Managed。
二、封装
由于.NET安全类库并没有直接提供加密文件的方法,而本文的重点是实现文件的加密,且考虑到编码的封装性原则,因此在.NET已有安全类库的基础上做进一步的封装,实现了一个多功能、高性能的文件加密系统,其中涉及了密码体系四个方面的技术:对称密码实现了DES和Rijndael算法,公钥密码实现了RSA算法,数字签名实现了DSA签名方案,消息摘要实现了MD5和SHA-1算法。下面对封装的方法和各部分功能的实现逐一分析。
1.对称密码
(1)DES(Data Encryption Standard)
.NET中DESCryptoServiceProvider类定义了访问DES算法的加密服务提供程序版本的包装对象,DESCryptoServiceProvider类的公共属性中有两个最重要的属性IV和Key。IV属性用于获取或设置对称算法的初始化向量,如果初始值为空,则调用GenerateIV方法创建新的随机值。Key属性用于获取或设置DES算法的密钥。如果此属性在使用时为空,则调用GenerateKey方法创建新的随机值。
DESCryptoServiceProvider类还有两个重要的公共方法:CreateEncryptor和CreateDecryptor。CreateEncryptor方法用指定的Key和IV创建DES加密器对象,而CreateDecryptor方法则用指定的Key和IV创建DES解密器对象。
基于以上,封装成一个新类MyDES。类声明如下:
class MyDES
{
private string _sourceFile; //需要加/解密文件的路径
private string _saveFile; //加/解密后的文件保存路径
private byte[] _DESKey; //DES的密钥,64位
private byte[] _DESIV; //DES的初始化向量,64位,已指定
public MyDES(string sourcepath, string savepath, string key); //类的构造函数
public void EncryptData(); //加密数据
public void DecryptData(); //解密数据
}
自定义的MyDES类与.NET中DESCryptoServiceProvider类之间的主要关系如图1所示。
图1 MyDES类与DESCryptoServiceProvider类之间的主要关系
为了简化加解密过程,用MyDES类中已给定值的成员_DESIV来取代原类库中DESCryptoServiceProvider类的IV属性;同时用MyDES类中的_DESKey来取代DESCryptoServiceProvider类的key属性,以便于从用户界面获得密钥。
构造函数MyDES()负责根据用户界面上获取的信息,初始化需要加/解密文件的路径_sourceFile、加/解密后的文件保存路径_saveFile以及DES的密钥_DESKey。
成员函数EncryptData()负责对指定的文件进行加密,主要步骤如下:
根据从用户界面获取的_sourceFile和_saveFile,创建输入输出流
FileStream fin = new FileStream(_sourceFile, FileMode.Open, FileAccess.Read);
FileStream fout = new FileStream(_saveFile, FileMode.OpenOrCreate, FileAccess.Write);
创建DESCryptoServiceProvider类的对象
DES des = new DESCryptoServiceProvider();
调用CreateEncryptor方法创建加密器对象,并将输入流套接成为加密流。
CryptoStream encryptStream = new CryptoStream(fout, des.CreateEncryptor(_DESKey, _DESIV), CryptoStreamMode.Write);
按每200字节块进行加密。
byte[] readBuffer = new byte[200];
len = fin.Read(readBuffer, 0, readBuffer.Length);
encryptStream.Write(readBuffer, 0, len);
清除所有敏感数据。
des.Clear();
创建DESCryptoServiceProvider类的对象后,应该显式调用其clear方法。因为从安全的角度考虑,仅在使用完对象后强制垃圾回收是不够的,必须显式调用Clear方法,以便在释放对象之前将对象中所包含的所有敏感数据清零。以下封装类都会用到此方法。
成员函数DecryptData()负责对指定的文件进行解密,其步骤与EncryptData类似。首先仍然是创建DESCryptoServiceProvider类的对象,调用其CreateDecryptor方法创建DES解密器对象,再将输入流套接成为加密流,利用此加密流进行解密工作。
(2)Rijndael(AES)
.NET中RijndaelManaged类定义了访问Rijndael算法的托管版本,同DESCryptoServiceProvider类一样,RijndaelManaged类的公共属性中也有两个最重要的属性IV和Key,以及两个重要的方法:CreateEncryptor和CreateDecryptor,它们的含义和DESCryptoServiceProvider类中的相同,不再赘述。基于以上,我们再封装一个类MyRijndael。类声明如下:
class MyRijndael
{
private string _sourceFile=null; //需要加/解密文件的路径
private string _saveFile=null; //加/解密后的文件保存路径
private byte[] _RijndaelKey=null; //Rijndael的密钥,256位
private byte[] _RijndaelIV= new byte[] { 166, 160, 56, 13, 136, 27, 144, 219, 162, 21, 60, 142, 182, 248, 1, 35 };//Rijndael的初始化向量,128位,已指定
public MyRijndael(string sourcepath, string savepath, string key); //类的构造函数
public void EncryptData(); //加密数据
public void DecryptData(); //解密数据
}
同样是为了简化加解密过程,在程序中显式指定了Rijndael的初始化向量。
DES和Rijndael都属于对称密码体制,只是基于的数学算法不相同。.NET基础类库中实现这两种算法的类结构也是极其相似的,因此封装的MyDES和MyRijndael也具有相同的接口。有关MyRijndael类的成员方法EncrptData和DecrptData的实现流程和说明可以参考MyDES类,不再重复。
2.公钥密码RSA算法
.NET中RSACryptoServiceProvider类定义了访问RSA 算法以执行不对称加解密的服务提供程序版本的包装对象。对于加密应用而言,RSACryptoServiceProvider类有四个常用且比较重要的公共方法Encrypt、Decrypt和FromXmlString、ToXmlString。
Encrypt方法、Decrypt方法分别使用RSA算法对数据进行加、解密。
FromXmlString方法通过XML字符串中的密钥信息初始化RSA对象,此方法可用于使用指定的密钥文件初始化RSA对象。
ToXmlString方法创建并返回包含当前RSA对象的密钥的XML字符串,此方法可用于程序中生成随机RSA密钥对并保存到文件。
基于以上,我们封装一个采用RSA公钥体制加密的类MyRSA。类声明如下:
public class MyRSA
{
private string _sourceFile; //需要加/解密文件的路径
private string _saveFile; //加/解密后的文件保存路径
private RSACryptoServiceProvider _rsa; //RSA加密服务提供程序的实现
public MyRSA(); //类的无参构造函数
public MyRSA(string sourceFile, string saveFile, string keyfile) : this(); //类的构造//函数
public string Key{ get; } //密钥对(公钥&私钥),提供只读访问器
public string PublicKey{ get; } //公钥,提供只读访问器
private void SaveToFile(string filename, string s); //将传入的字符串写入指定文件
private string ReadFile(string filename); //读取文件
public void LoadKeyFromFile(string filename); //载入密钥文件
public void SaveKeyToFile(); //保存密钥到文件
//清理资源,对外提供使内部对象中所包含的所有敏感数据清零的方法
public void Clear();
public void EncryptData(); //使用公钥进行加密
public void DecryptData(); //使用私钥进行解密
}
自定义的MyRSA类与.NET中RSACryptoServiceProvider类之间的主要关系如图2所示。
图2 MyRSA类与RSACryptoServiceProvider类之间的主要关系
无参构造函数的作用很简单,生成一个RSACryptoServiceProvider类的对象。
构造函数的另一个重载版本负责根据从界面上获取的信息初始化需要加/解密文件的路径、加/解密后的文件保存路径以及需要载入的密钥文件路径。
公开Key和PublicKey属性是为了便于生成随机密钥对,因此仅提供只读访问器。
LoadKeyFromFile封装了上文提到的FromXmlString方法,作用是从指定文件载入密钥。
SaveKeyToFile封装了上文提到的FromXmlString方法,作用是将生成的随机密钥对保存到文件。
Clear只是简单地封装了RSACryptoServiceProvider类的Clear方法,目的是为了在用MyRSA类的对象生成随机密钥后能有一个及时清除所有敏感数据的方法。
EncryptData负责对指定的文件进行加密,主要步骤如下:
根据从用户界面获取的_sourceFile和_saveFile,创建输入输出流
FileStream fin = new FileStream(_sourceFile, FileMode.Open, FileAccess.Read);
FileStream fout = new FileStream(_saveFile, FileMode.OpenOrCreate, FileAccess.Write);
按128字节分块读取文件,利用RSACryptoServiceProvider类的Encrypt()方法加密。
byte[] readBuffer = new byte[128];
fin.Read(readBuffer, 0, readBuffer.Length);
byte[] encryptedBuffer = _rsa.Encrypt(readBuffer, true);
其中_rsa为构造函数中生成的RSACryptoServiceProvider类的对象。
清除所有敏感数据。
_rsa.Clear();
DecryptData负责解密数据,同EncryptData类似,首先仍然是从文件路径创建输入输出流,分块读取输入流,调用RSACryptoServiceProvider的Decrypt方法解密,再将解密后的数据块写进输出流,最后调用Clear方法清除内存中的敏感数据。
3.数字签名DSA(Digital Signature Algorithm)算法
.NET中DSACryptoServiceProvider类定义了访问DSA 算法以执行数字签名的服务提供程序版本的包装对象。DSACryptoServiceProvider类有四个常用的公共方法FromXmlString、ToXmlString、SignData和VerifySignature。前两个方法同RSA中的同名方法,另两个方法说明如下:
SignData方法有三个重载,在本程序中需要输入参数是流的重载,这个重载计算指定输入流的Hash值并对其签名,生成Hash的算法采用的是DSA中默认的SHA-1。
VerifySignature方法验证指定数据的DSA签名,接受文件的Hash值和需要验证的签名数据作为输入参数。
基于以上,封装一个采用DSA签名方案的类MyDSA。类声明如下:
class MyDSA
{
private string _path1; //需要签名的文件或者接受到的文件路径
private string _path2; //签名文件的路径
private DSACryptoServiceProvider _dsa; //DSA服务提供程序的实现
public MyDSA(); //类的构造函数
public MyDSA(string path1, string path2, string keyfile): this(); //类的构造函数
public string Key{ get; } //密钥对(公钥&私钥),提供只读访问器
public string PublicKey{ get; } //公钥,提供只读访问器
private void SaveToFile(string filename, string s); //将传入的字符串写入指定文件
private string ReadFile(string filename); //读取文件
public void LoadKeyFromFile(string filename); //载入密钥文件
public void SaveKeyToFile(); //保存密钥到文件
//清理资源,对外提供使内部对象中所包含的所有敏感数据清零的方法
public void Clear();
public void SignData(); //签名文件
public bool VerifySignature(); //验证签名
}
自定义的MyDSA类与.NET中的DSACryptoServiceProvider类之间的主要关系如图3所示:
图3 MyDSA类与DSACryptoServiceProvider类之间的主要关系
MyDSA类中的两个重载构造函数LoadKeyFromFile方法、SaveKeyToFile方法和Clear方法的作用均与MyRSA中的类似,在此不再赘述。
SignData方法负责对文件签名,具体步骤如下:
根据从用户界面获得的_path1和_path2,创建输入输出流
FileStream originalFile = new FileStream(_path1, FileMode.Open, FileAccess.Read);
FileStream fout = new FileStream(_path2, FileMode.OpenOrCreate, FileAccess.Write);
利用DSACryptoServiceProvider类的SignData方法签名文件
byte[] signedHashValue = _dsa.SignData(originalFile);
其中_dsa为构造函数中生成的DSACryptoServiceProvider类的对象。
清除所有敏感数据
_dsa.Clear();
DSACryptoServiceProvider类的SignData方法存在一个接受流的重载,采用默认的SHA-1算法生成流的Hash值再将Hash值用私钥签名。最后,将签名结果写入输出流并调用clear方法清除内存中的敏感数据。
VerifySignature方法负责验证签名,首先利用SHA1CryptoServiceProvider类的ComputeHash计算文件的哈希,接着调用DSACryptoServiceProvider类的VerifySignature方法验证此数值与用公钥解密签名文件内容后的数值是否相等。
DSA在计算Hash值时默认采用SHA-1算法。如果想自定义生成Hash的算法,可以采用System.Security.Cryptography命名空间下的DSASignatureFormatter类,此类中的SetHashAlgorithm方法可以指定DSA的Hash算法,然后再调用其CreateSignature方法生成签名,下面给出代码片段:
//创建一个 DSACryptoServiceProvider类的对象
DSACryptoServiceProvider DSA = new DSACryptoServiceProvider();
//待签名的哈希值
byte[] Hash = {59,4,248,102,77,97,142,201,210,12,224,93,25};
//创建一个DSASignatureFormatter类的对象,接受一个 DSACryptoServiceProvider类的
//对象作为参数以传递密钥信息
DSASignatureFormatter DSAFormatter = new DSASignatureFormatter(DSA);
//设置哈希算法为SHA1
DSAFormatter.SetHashAlgorithm("SHA1");
//生成哈希值的签名并返回
byte[] SignedHash = DSAFormatter.CreateSignature(Hash);
4.消息摘要
.NET中的MD5CryptoServiceProvider和SHA1CryptoServiceProvider类分别定义了用MD5算法和SHA-1算法以计算数据的Hash值的包装对象。在这两个类中有一个共同的从HashAlgorithm类继承的方法ComputeHash,此方法有三个重载,用于在派生类中用特定的算法计算数据的Hash值,在这里使用传入参数是流的重载。
基于以上,封装一个集成MD5和SHA-1算法的类MessageDigest,此类为一个静态类。类声明如下:
static class MessageDigest
{
//将输入的字节数组转换为16进制表示的字符串。
private static string ArrayToHexString(byte[] array, bool uppercase);
//计算文件的MD5值
public static string ComputeFileMD5(string filePath);
//计算文件的SHA-1值
public static string ComputeFileSHA1(string filePath);
}
ComputeFileMD5方法用于文件MD5值的计算,其主要步骤如下:
根据接受的文件路径参数创建文件流
FileStream fs = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read);
创建MD5CryptoServiceProvider类的对象并调用ComputeHash方法计算哈希值
MD5 md5 = new MD5CryptoServiceProvider();
byte[] byteTemp = md5.ComputeHash(fs);
清除所有敏感数据
md5.Clear();
ComputeFileSHA1方法用于文件SHA1值的计算,其步骤同ComputeFileMD5类似,不同之处在于创建的是SHA1CryptoServiceProvider类的对象。
三、程序运行
本文的程序在Visual Studio 2005的开发环境下实现,封装了.NET基础类库,因此程序运行需要.NET Framework 2.0或更高版本的支持。Microsoft .NET Framework 2.0版可从如下地址获得:
http://www.microsoft.com/downloads/details.aspx?FamilyID=0856eacb-4362-4b0d-8edd-aab15c5e04f5&displaylang=zh-cn
在确保机器上已正确安装了.NET Framework 2.0以后即可运行程序。
通过选择不同的标签页可以执行相应的加解密、签名或消息摘要算法。在程序中还增加了时间测试功能,这样每一项操作执行完毕后均会给出提示并显示精确的用时,详细代码请见源程序。程序的DES和MD5运行界面如图4、图5所示。
图4 DES操作界面
图5 MD5操作界面
四、系统性能
从横向和纵向两个方面对程序做对比测试,测试环境如表1所示。横向对比中,分别就对称密码、公钥密码、数字签名和消息摘要四组来对文本处理的结果进行性能分析;纵向测试中,将本程序与VC平台下实现的其他文件加密软件作了性能对比。限于篇幅,程序执行时间的统计实现请见源代码。
表1 测试环境
硬件环境 |
CPU |
Intel® Core™2 T5500@1.66GHz |
内存 |
DDRⅡ-667 1.5GB |
显卡 |
ATI Mobility Radeon X1300 |
软件环境 |
操作系统 |
Microsoft Windows XP SP2 |
测试程序版本 |
Release |
表2给出了横向测试的结果,需要说明的是,由于RSA对于大文件的解密操作耗时过多,放弃了两次测试,表中对应结果用N/A标识,但是从RSA对于小文件的测试结果可以预测出趋势。
(注:对比程序给出的运算用时仅精确到秒,下同)
通过分析测试所得数据,可以得出如下一些结论:
在相同测试条件下,DES的加密速度比解密速度快,而Rijndael的解密速度比加密速度快,但这只有在操作较大文件时才有明显差异。另外,DES的加解密均比Rijndael的加解密快,这是由算法的数学理论基础决定的。
在相同测试条件下,RSA的解密算法比其加密算法耗时慢得多,平均是加密耗时的20倍左右。而无论RSA的加密还是解密耗时均比对称密码(DES或Rijndael)要慢得多,平均是对称密码耗时的30倍左右。这是由于RSA进行的都是大数计算,因此无论是软件还是硬件实现,速度一直是RSA的缺陷。一般来说RSA只用于少量数据加密,其更广泛的应用则是进行签名和认证。
DSA是对数据采用默认的SHA-1算法计算Hash值后用私钥进行签名,私钥签名运算非常快,这是由于Hash值的数据量非常小,因此,DSA的运算性能主要取决于SHA-1算法产生文件Hash值的效率。从表2中数据可以看出,对于相同文件,DSA的签名耗时和SHA-1计算其Hash值的耗时是非常接近的,两者耗时之差可以认为是DSA用私钥对哈希值进行签名的耗时,这个值稳定在0.060秒左右。
MD5和SHA-1均用于生成消息摘要,可以看出,它们的运算速度是非常快的,而且随着文件的变大,运算速度没有明显的改变。
表3给出了纵向测试的结果,由于各程序随机生成的公钥密码不相同,在不同密钥对的条件下对于公钥密码的性能不具有可比性,因此纵向测试仅选择DES和MD5两个项目进行。其中,DES选取的对比程序是由东北大学信息科学与工程学院安全与保密小组制作的一个混合密码系统,并且在DES测试时选取了相同的8字符密钥,MD5选取的是网络上常见的MD5校验工具,这两个程序均是基于VC平台实现的。
表3 纵向测试结果
用 测
时(s) 试
文件大小 |
DES |
|
MD5 |
.NET程序 |
对比程序 |
.NET程序 |
对比程序 |
加密 |
解密 |
加密 |
解密 |
1,830 K |
0.235 |
0.250 |
2 |
3 |
0.016 |
0 |
22,960 K |
2.125 |
2.532 |
30 |
30 |
0.156 |
0 |
113,822 K |
7.531 |
10.094 |
152 |
153 |
0.672 |
2 |
从以上对比结果可以很清楚地看出,利用.NET基础类库实现的DES加解密和计算文件MD5值的算法具有优秀的性能,而且在处理大文件时表现更佳。这是由于.NET基础类库对于文件流的输入输出和加密的核心算法在程序设计级别上做了相当的优化。可以相信,.NET安全类库实现的其他加密算法同样也具有优秀的性能。
五、结语
本文针对.NET基础类库在system.security.cryptography命名空间下实现的诸多加密服务提供类进行了介绍,在此基础上,封装了基础类库中的相关安全类,实现了一个多功能、高性能的加密系统。最后,还将该程序和VC平台下实现的其他加密程序进行了性能比较。
另外,从微软的官方文档来看,.NET Framework 对于数据加密/解密支持是比较好的,这大大地方便了开发人员,但美中不足的是,.NET Framework 中的数据加密算法仍然不够完全,如IDEA、BLOWFISH算法,还有其他如ElGamal、Deffie-Hellman、ECC等算法没有包含,对于一些其他的数据校验算法如CRC、SFV等支持也不够,这有待于在未来的.NET Framework版本中得以完善。
|