DES算法为密码体制中的对称密码体制,又被成为美国数据加密标准,是1972年美国IBM公司研制的对称密码体制加密算法。
其密钥长度为56位(实际上密钥为64位而DES只用到其中56位),明文按64位进行分组,将分组后的明文组和56位的密钥按位替代或交换的方法形成密文组的加密方法。
DES加密算法特点:分组比较短、密钥太短、密码生命周期短、运算速度较慢。
DES工作的基本原理是,其入口参数有三个:key、data、mode。 key为加密解密使用的密钥,data为加密解密的数据,mode为其工作模式。当模式为加密模式时,明文按照64位进行分组,形成明文组,key用于对数据加密,当模式为解密模式时,key用于对数据解密。实际运用中,密钥只用到了64位中的56位,这样才具有高的安全性。
DES算法把64位的明文输入块变为64位的密文输出块,它所使用的密钥也是64位,整个算法的主流程图如下:
以上是DES原理的简单介绍,对于DES具体实现没有必要深入了解,但需要知道DES的几个特性
1、DES是对称加密算法,加密和解密都需要同一个64位密钥
2、DES是可逆加密算法,从DES的原理中也可以看到这一点
3、加密前后数据长度可能是不一致的,DES将明文按64位进行分组,长度为0~8字节的明文加密后密文为8字节,长度为9的明文加密后密文为16字节,以此类推
.net中提供了DESCryptoServiceProvider类用于DES加解密,其父类是DES类,一个抽象类
//形式一 DES des = new DESCryptoServiceProvider(); //形式二 DESCryptoServiceProvider des = new DESCryptoServiceProvider();
使用DESCryptoServiceProvider进行加密和解密时,除了要提供待加解密的数据外和一个64位(8字节)的Key外,还需要提供一个IV(初始化向量),IV的长度与Key一样,可以自定义,也可以由GenerateIV方法生成,加解密时所使用的IV也必须是同一个
MSDN中关于这个IV的解释是:
每当您创建其中一个 SymmetricAlgorithm 类的新实例时,或者当您手动调用 GenerateIV 方法时,IV 属性均自动设置为新的随机值。IV 属性的大小必须与 BlockSize 属性的大小相同。
从 SymmetricAlgorithm 类派生的类使用一种称为密码块链接 (CBC) 的链接模式,该链接模式需要密钥和初始化向量才能执行数据的加密转换。若要解密使用其中一个 SymmetricAlgorithm 类加密的数据,必须将 Key 属性和 IV 属性设置为用于加密的相同值。
对于给定的密钥 k,不使用初始化向量的简单块密码将同一个纯文本输入块加密为同一个密码文本输出块。如果您的纯文本流中有重复块,则您的密码文本流中也会有重复块。如果未经授权的用户知道了您的纯文本块结构的任何信息,他们就可以利用该信息来解密已知的密码文本块,并有可能重新获得您的密钥。为了防止这个问题,前一个块中的信息被混合到下一个块的加密过程中。这样一来,两个相同的纯文本块的输出就变得不一样了。由于此技术使用前一个块加密下一个块,因此需要初始化向量来加密数据的第一个块。
以下是一个DES加解密的范例代码
class DesHelper { //自定义IV,长度与Key一致,都应为8个字节 private static byte[] iv = { 0x12, 0x34, 0x56, 0x78, 0x90, 0xAB, 0xCD, 0xEF }; /// <summary> /// 处理不合格的Key /// </summary> private static string BuildKey(string key) { if (string.IsNullOrEmpty(key)) //处理空,不使用DES的GenerateKey,它是随机的 key = "Y09M05#X"; else if (key.Length < 8) //处理不足8位,使用PadRight key = key.PadRight(8, 'X'); else if (key.Length > 8) key = key.Substring(0, 8); //处理大于8位 return key; } /// <summary> /// 字符串加密 /// 异常返回string.Empty(string.Empty <> null),仅为演示用,不建议采用这种完全包住异常的方式,应尽量让调用者捕获异常 /// </summary> public static string EncryptString(string key, string text) { //将string的Key转换为Byte数组,为与DecryptString保持一致,这里使用Encoding的UTF8转换,而不是Default byte[] bKey = Encoding.UTF8.GetBytes(BuildKey(key)); //设置iv byte[] bIV = iv; //将待加密数据转换为Byte数组,注意这里使UTF8,DecryptoString应与之保持一致 byte[] bValue = Encoding.UTF8.GetBytes(text); //创建DES对象 DESCryptoServiceProvider des = new DESCryptoServiceProvider(); //初始化变量为null MemoryStream mStream = null; CryptoStream cStream = null; string base64 = string.Empty; try { //将MemoryStream作为目标 mStream = new MemoryStream(); //创建CryptoSteam对象,并附加到mStream,注意这里的des.CreateEncryptor()和CryptoStreamMode.Write cStream = new CryptoStream(mStream, des.CreateEncryptor(bKey, bIV), CryptoStreamMode.Write); //将bValue中的明文加密后写入目标流mStream cStream.Write(bValue, 0, bValue.Length); //Flush一下 cStream.FlushFinalBlock(); //将加密结果转换为友好的Base64 base64 = Convert.ToBase64String(mStream.ToArray()); } catch (Exception ex) { Trace.WriteLine(ex.Message); return string.Empty; } finally { if (cStream != null) cStream.Close(); if (mStream != null) mStream.Close(); } return base64; } /// <summary> /// 字符串解密 /// 需捕获异常 /// </summary> public static string DecryptString(string key, string base64) { //与加密一致,使用UTF8 byte[] bKey = Encoding.UTF8.GetBytes(BuildKey(key)); //设置iv,解密iv应与加密iv一致 byte[] bIV = iv; bIV[1] = 0x12; //传入非Base64字串时将触发异常 byte[] bValue = Convert.FromBase64String(base64); //创建DES对象 DESCryptoServiceProvider des = new DESCryptoServiceProvider(); //这里将MemoryStream为作源 MemoryStream mStream = new MemoryStream(bValue); CryptoStream cStream = null; string plainText = string.Empty; try { //创建CryptoStream对象并附加到mStream,注意这里是des.CreateDecryptor和CryptoStreamMode.Read cStream = new CryptoStream(mStream, des.CreateDecryptor(bKey, bIV), CryptoStreamMode.Read); //读取一个字节以测试待解密数据是否标准 cStream.ReadByte(); //当解密Key与加密Key不一致或传入不正确的DES加密数据时将触发异常 } catch (CryptographicException ex) { //这里不调用cStream.Close(),因为CryptoStream有一个奇怪的特性,如果数据流没有读至末尾,调用Close会触发异常,而这里解密Key或待解密数据本身就不正确,也就没法读至末尾 //cStream.Close(); mStream.Close(); //重新抛出异常 throw ex; } //CryptoStream的Seek方法无效,所以这里只能关闭重来 cStream.Close(); mStream.Close(); mStream = new MemoryStream(bValue); cStream = new CryptoStream(mStream, des.CreateDecryptor(bKey, bIV), CryptoStreamMode.Read); //创建一个StreamReader对象并附加到cStream,注意这里使用UTF8,与EncryptoString保持一至 StreamReader sReader = new StreamReader(cStream, Encoding.UTF8); //从源流mStream中读取密文,经过cStream转换,最后使用StreamRead的Read读出明文,注意这里是ReadToEnd而不是ReadLine plainText = sReader.ReadToEnd(); //关闭对象,注意顺序 sReader.Close(); cStream.Close(); mStream.Close(); return plainText; } /// <summary> /// 文件加密 /// 需要捕获异常 /// </summary> public static void EncryptFile(string key, string inFile, string outFile) { byte[] bKey = Encoding.UTF8.GetBytes(BuildKey(key)); byte[] bIV = iv; FileStream inStream = null; FileStream outStream = null; CryptoStream cStream = null; try { inStream = new FileStream(inFile, FileMode.Open, FileAccess.Read); outStream = new FileStream(outFile, FileMode.Create, FileAccess.Write); byte[] buffer = new byte[1024]; long inLen = inStream.Length; long i = 0; int rdLen = 0; DESCryptoServiceProvider des = new DESCryptoServiceProvider(); //注意这里使用的是Write cStream = new CryptoStream(outStream, des.CreateEncryptor(bKey, bIV), CryptoStreamMode.Write); while (i < inLen) { //从明文文件中读入明文数据 rdLen = inStream.Read(buffer, 0, 100); //向加密文件写出密文数据 cStream.Write(buffer, 0, rdLen); i += rdLen; } } finally { if(cStream != null) cStream.Close(); if(outStream != null) outStream.Close(); if(inStream != null) inStream.Close(); } return; } /// <summary> /// 文件解密 /// 需要捕获异常 /// </summary> public static void DecryptFile(string key, string inFile, string outFile) { byte[] bKey = Encoding.UTF8.GetBytes(BuildKey(key)); byte[] bIV = iv; FileStream inStream = null; FileStream outStream = null; CryptoStream cStream = null; try { inStream = new FileStream(inFile, FileMode.Open, FileAccess.Read); outStream = new FileStream(outFile, FileMode.Create, FileAccess.Write); const int bufferSize = 1024; byte[] buffer = new byte[bufferSize]; int rdLen = 0; DESCryptoServiceProvider des = new DESCryptoServiceProvider(); try { //注意这里是Read cStream = new CryptoStream(inStream, des.CreateDecryptor(bKey, bIV), CryptoStreamMode.Read); Trace.WriteLine("ssss"); do { //从密文文件中读取数 rdLen = cStream.Read(buffer, 0, 1024); //当解密Key与加密Key不一致或传入不正确的DES加密数据时将触发异常 //将解密数据输出明文文件,长度是是cStream.Read的返回 outStream.Write(buffer, 0, rdLen); } while (rdLen != 0); } catch (Exception ex) { Trace.WriteLine(ex.Message); cStream = null; throw ex; } } finally { if (cStream != null) cStream.Close(); if (outStream != null) outStream.Close(); if (inStream != null) inStream.Close(); } } }
在.net中进行DES加解密要注意以下几点:
1、Key和IV的长度必须为8字节,key也不能太简单,否则DES.CreateXXcryptor()会抛出异常
2、Key如果是string就必须转换为byte数组,加解密的Key的转换应保持一至,比如都是Encoding.UTF8.GetBytes,而不能是Default这种不确定的代码
3、解密时要注意可能传入的Key与加密Key不同,这会使CryptoStream.Read()抛出异常,Write不会
4、没有方法判断IV的一至性
5、解密时要注意可能传入不正确的Base64字串,这会使Convert.FromBase64String()抛出异常
6、解密时要注意可能传入不正确的密文,如“test”,这会使CryptoStream.Read(()抛出异常
7、解密时CryptoStream既可以使用Write模式,也可以使用Read模式创建,Write和Read方法的原型如下
public override void Write ( byte[] buffer, int offset, int count ) public override int Read ( [InAttribute] [OutAttribute] byte[] buffer, int offset, int count )
以下是一段使用Write方法进行解密的代码片段
//将mStream作为目标 mStream = new MemoryStream(); //这里使用Write cStream = new CryptoStream(mStream, des.CreateDecryptor(bKey, bIV), CryptoStreamMode.Write); //将bValue中的密文解密后写入mStream cStream.Write(bValue, 0, bValue.Length); //必须的,否则没有输出 cStream.FlushFinalBlock(); //以UTF8获取String plainText = Encoding.UTF8.GetString(mStream.ToArray());
解密时,CryptoStream的Write方法将buffer(源)中的密文转换为明文,并将明文自动写入与之绑定的Stream(目标)中,Write方法没有返回值以说明写入了多长的明文,我们知道DES算法的明文和密文长度是不能保证一致的,这里只能通过其它方法判断明文长度,而CryptoStream的Read方法将与之绑定的Stream(源)中的密文数据转换为明文,并将明文写入buffer(目标)中,Read方法返回写入明文的实际长度。
更重要的是,如果Key与加密Key不一至或传入了不正确的密文,CryptoStream的Read将抛出异常,也就是说通过捕获这个异常可以判断Key或密文是否正确,而如果是使用Write则CryptoStream不作判断,转换可以被进行下去,虽然这是错误的
因此建议的作法是加密使用CryptoStreamMode.Write,解密使用CryptoStreamMode.Read
8、以Read模式构造的CryptoStream,内容没有全部读出前Close流的话,就会抛出异常,以下是一个解决这个问题的代码片段
finally { if (sReader != null && sReader.BaseStream != null) { sReader.ReadToEnd(); sReader.Close(); } if (cStream != null) { while (cStream.ReadByte() != -1) { cStream.ReadByte(); } cStream.Close(); } if (mStream != null) { mStream.Close(); } }
但这又有一个矛盾,解密时如果key或密文不正确,Read操作也会抛出异常,也就是说在这种情况下根本没办法Close掉CryptoStream对象
9、注意字符串和二进制数据密文解密处理代码的不同
10、DESHelper的代码还涉及到另一个问题:异常捕获策略,这请参考相关文档
参考资料:
DES算法
http://baike.baidu.com/view/584868.htm
DES类
http://msdn.microsoft.com/zh-cn/library/system.security.cryptography.des.aspx
关于3DES的解密
http://social.microsoft.com/Forums/zh-CN/2212/thread/46d5d9b3-bfd3-4dc4-9a69-b2f2a04ce049
如何使用 Visual C# 加密和解密文件
http://support.microsoft.com/kb/307010/zh-cn
使用 C# .NET 加密和解密文件
http://www.2mysite.net/blogview.asp?logID=695
.NET C# DES入门
http://blog.csdn.net/dcsoft/archive/2006/08/21/1103782.aspx
C# DES 加密解密类
http://www.cnblogs.com/yiki/archive/2007/08/28/873043.html
C# DES 加密-解密类库,支持文件和中文-UNICODE字符,返回BASE64编码字符串
http://www.zu14.cn/2009/05/26/csharp-des-library-support-file-base64-encoding/
C#里的一些加密解密标准函数示例
http://blog.csdn.net/gisfarmer/archive/2009/04/15/4074912.aspx