所谓加密就是将数据进行不规则化以保证源数据机密性的机制或进行签名以保证数据完整性。特别是如今电子商务的火热和人们对隐私的注重,加密对于变通的程序员来说,也是必须考虑的问题了。如何不规则化数据呢,我们自己可能就会提出很多种方案,也就是一种加密算法,至于安全性可能就跟本身的设计有关了;而现如今就有很多公开的算法如DES、RSA、MD5等等,虽然算法是公开的,但是他们根据密钥来加密,想要解密它们就需要解密的密钥甚至有些是不能得到源数据的。那么.net提供哪些加密的方式呢?
.net程序员最常用的肯定就是FormsAuthentication.HashPasswordForStoringInConfigFile,功能是根据指定的密码和哈希算法生成一个适合于存储在配置文件中的哈希密码,当然哈希密码要存储在哪我们有绝对的控制权,大多数时候我们把用户密码加密之后,存储到数据库里。它需要两个参数,一个是要加密的字体串,另一个是要使用的加密算法的字符串表示:Clear、MD5、SHA1,第一个表示不加密,后两种是不同的加密算法。
这种算法是单向加密,就是说在你想从密文本身解出源数据几乎是不可能的。好像很安全,其实不然。它是有弊端的:不管什么时候什么情况,对同一个数据加密得到的数据是一样的。这本身没什么啊,但是这其实是不安全,我只要建立足够多的数据源数据和密文的对照库,就非常可能从其中库中查询到源数据,成功不成功,安全取决于你这个库的强大与否。如果你不信,可以在百度上搜MD5,会出现很多“MD5解密”的结果,随便找一个试一下。。。
这种算法,也是我们下面将要提到的哈希加密算法,它从一段信息中产生一个哈希值,即使你更改其中任何一个字符,都会产生另外一个哈希值,因此它主要用于数据完整性的验证,并不适合用于保存私密信息。
.NET的加密类分为三层,第一层是抽象的基类,用于完成加密任务,这些抽象类分别是:
AsymmetricAlgorithm:它提供非对称的加密算法,就是解密加密分别要使用不同的密钥完成。
SymmetricAlgorithm:相对于上一个,提供的对称加密算法,解密加密使用的同一个密钥。
HashAlgorithm:它提供是散列的生成算法和验证机制。刚刚提到的MD5就属于这个算法的范畴。
第二层代表一个指定的加密算法类,它们从加密基类继承而来,但仍然是抽象类。如DES继承自SymmetricAlgorithm,它代表DES算法。
第三层是一系列加密的实现,每一个实现类都是从一个加密算法继承而来。同样一个加密算法类可能有多个实现类。.net的加密类有的完全是在托管代码中实现的,而有一些是对非托管的CryptoAPI库的薄封装,作为一个约定,封装CryptoAPI的类它们的名字中都含有CryptoServiceProvider,而托管类在它们的名字都含有Managed。
从上图可以看到,.net提供4种加密算法:DES (64)、TripleDES (128、192)、RC2 (40-128)、Rijndael (128、192、256),括号内数字是该算法有效密钥长度。加密的强度和密钥的长度有关,密钥越长,攻击都所要测试的数据就会越多,相对就越安全,但是同时也会使得加密解密的时间变长。对于大多数程序来说,Rijndael是一个较好的选择,性能可靠又支持较长的密钥。
对称加密最大的好处是它的性能,但是它也存在以下一些问题:1,密钥的交换:你必须选择一种安全的方式交换密钥;2,暴力破解:如果长时间使用一个密钥,攻击都就有可能有足够的时间试出密钥;3,密钥的管理:你如果定期更换密钥,交换密钥及原来数据的支持都会是一个问题,还要双方在一个安全的地方保存。
为了试图解决这些问题,非对称加密算法出现了。
非对称加密算法,需要两个密钥:一个加密密钥,通常称为公钥;一个是解密密钥,私钥。通常我们把公钥分发那些需要加密数据的人,而私钥只有自己保存。用公钥加密的数据只能用私钥才能解密。
RSA,支持直接的加密和解密,有效密钥长度384-16384。而RSA(数字签名算法)只能用于对信息进行签名和验证签名。
非对称加密对公钥完全公开,也就不存在对称加密几个问题,但是它的问题就是性能问题,如果在系统中多次交换数据或交换大量数据可能会影响系统性能。
难道没有一种较好的解决方案吗,即兼顾性能,又安全?答案是肯定的,让我们看一下SSL,我们知道SSL技术是http上交换的信息进行加密常用的,它被大量浏览器和服务器支持,用来保证之间传输的数据不会被轻易的窃听或破解。
让我们大概看一上它的实现方案,它使用了对称加密和非对称加密两种方式:
从你输入一个https的URl点击enter键开始:
1,客户端发送一个请求;
2,服务器会对自己的证书签名然后发给客户端,证书中包含非对称加密的公钥,私钥当然自己保存;
3,客户端验证这个证书是自己信任的机构颁发的,然后验证证书,如果验证通过接受连接;
4,然后客户端告诉服务自己支持什么类型的加密密钥;
5,服务端选择支持的最强的密钥长度告诉客户端;
6,在双方协调完毕,浏览器根据指定的密钥长度生成一个对称加密的密钥,浏览器用获得的公钥加密这个密钥,把他传给服务器;
7,服务器用自己保存的私钥解密这个密钥,获取原始的对称加密的密钥,这样双方就都保存了这份对称加密密钥,那么在此次会话以后的通信中就会用这个密钥加密和解密数据。
也就是说在一次会话中,只使用了一次非对称加密,并且只是对一个密钥加密解密,而其它多次大量数据的加密解密都是对称加密来解决的,保证了性能;由于是浏览器端生成对称加密密钥并且采用非对称加密算法加密,只有私钥才能解密这个密钥,保证对称加密密钥的交换的安全性;并且这个密钥只在此次会话有效,从时间上减少了风险。
我们知道,加密类的第一层和第二层都是抽象的,抽象加密类它主要两个好处:
1,定义加密实现类要支持的基本成员;
2,它们通过静态的Create()方法,来直接创建一个实现类的实例。
例如:
DES des = DES.Create();
它返回了默认的DES算法的实现类,实际上它是DESCryptoServiceProvider实例,这样做的优点就是面向对象的一些优点,如果某个时候,我们更改了DES的默认实现,但是我们这块使用的代码却可以不做任何修改,只要这个实现仍然是从DES继承而来。
另外各个算法除了实现加密和解密的功能之法,同时还提供了GenerateKey()方法:它会生成一个符合对应算法要求的密钥。
.net使用一个基于流的架构来实现加密和解密,这样我们可以容易的加密或解密来自不同数据源不同类型的数据和多重加密。这其中最核心的类:ICryptoTransform接口和CryptoStream类。
ICryptoTransform定义了基于块的转换的操作:这可能是加密、解密、哈希、base64编码、解码或格式化操作。
DES des = DES.Create(); ICryptoTransform EnTransform = des.CreateEncryptor(); ICryptoTransform DeTransform = des.CreateDecryptor();
通过以上方法,我们可以基于一个加密类创建一个加密或解密的操作。各种加密任务都以相同的方式执行,但是每个加密操作在数据需要处理之前都被分为固定大小的块,因此大多数时候我们选择一个更简单的方式:把它传递给另一个类:CrpytoStream。
CrpytoStream包装一个流,并使用ICryptoTransform在后台执行它的工作。CrpytoStream使用缓冲访问,因此可以自动加密而不用担心算法所要求块的大小;另外它可以封闭任何继承于.net流的类如FileStream、MemoryStream或NetworkStream等,这样我们很容易的在其它类操作之上使用。
创建CrpytoStream的实例,需要三个信息:底层的数据流、模式(读或写)、要使用的ICryptoTransform。
/// <summary> /// 加密 /// </summary> /// <param name="data">要加密的数据</param> /// <returns></returns> public byte[] EncrytData(String data) { // 转化为流使用的字节数组 byte[] byData = Encoding.UTF8.GetBytes(data); // 加密算法实例 DES des = DES.Create(); // 加密操作 ICryptoTransform EnTransform = des.CreateEncryptor(); // 生成密钥,这里只是演示。密钥应该之前生成,在此外使用或在此处生成然后以安全的方式保存、交换,以供解密用 des.GenerateKey(); byte[] cryptoKey = des.Key; // 加密的信息 MemoryStream stream = new MemoryStream(); // 创建加密流 CryptoStream cs = new CryptoStream(stream, EnTransform, CryptoStreamMode.Write); // 向stream写入加密的信息 cs.Write(byData, 0, byData.Length); // 更新缓冲区状态到基础数据源 cs.FlushFinalBlock(); // 返回加密后的信息的字节数组形式 return stream.ToArray(); }
得到加密信息可以存储或者发送给其它程序。
相对于加密,当然也要有解密:
/// <summary> /// 解密 /// </summary> /// <param name="data">要解密的数据</param> /// <param name="key">解密的密钥,和上面的加密密钥一定要是一样的</param> /// <returns></returns> public String DecrytData(byte[] data,byte[] key) { // 算法实例并确定解密密钥 DES des = DES.Create();
des.Key = key; // 解密操作 ICryptoTransform DeTransform = des.CreateDecryptor(); // 信息 MemoryStream stream = new MemoryStream(); // 创建流 CryptoStream cs = new CryptoStream(stream, DeTransform, CryptoStreamMode.Write); // 向stream写入解密后的信息 cs.Write(data, 0, data.Length); // 更新缓冲区状态到基础数据源 cs.FlushFinalBlock(); // 返回解密后的字符串形式 return Encoding.UTF8.GetString(stream.ToArray()); }
这里只是一个简单的例子,只是加密和解密的一些基本步骤,至于密钥的交换和保存就看自己实际情况进行妥善处理了。
如何能让系统更安全,这是开发者要最先考虑的问题,而加密是我们最常用也比较好用的保证安全的方式,我们应该对此尽可能的了解,然后才能做出优良的选择和设计。