日期:2012-01-16  浏览次数:20397 次

密码的故事
Billy Hollis
2002 年 3 月 14 日

本文是由一个问题引出的。我需要一种将密码保存在加密文件中的方法,因为我需要记住许多密码,但记忆力却已大不如前。我知道有许多商用工具能够做到这一点,但我感到学习 .NET 中的一项新技术真的很有好处。
我用 Visual Basic® .NET 完成了一个简单而完整的程序,用于加密和解密文件,从中学到了许多知识。既然加密对于多种开发都是一个重要问题,本文就介绍一下如何构造这样的程序。
有各种低级别的技术可以用于加密,如 Microsoft Crypto API。而在 .NET 中,则是将这些复杂内容打包在各个 .NET 框架类中,并且由一个 System.Security 命名空间包含这些与加密相关的类。我们不可能查看该命名空间中的所有类,但通过分析一个最简单的、使用数据加密标准 (DES) 算法进行加密和解密的类,可以大概了解它们的工作原理。
正如前面提到的,我们要执行一个完整的加密和解密文件的过程,但首先需要解释一下该程序中涉及的许多基本概念。除有关密码的原理外,还有必要简单讨论一下 .NET 中的流,因为加密类是以流的形式实现的。

理解流


流是 .NET 中处理字节的基本概念。下面简单介绍一下其工作原理。
假设要读取一个文件,将所有大写字母更改为小写字母,然后将结果写入另一个文件。图 1 显示了要完成的各个步骤的关系图。

图 1:读取文件、处理内容并写回结果的过程
在 .NET 中,完成此过程的最好方法是使用流。“流”是一个对象,用于接收和/或发送信息字节。流有两种 - 后端流和过程流。

后端流


后端流从某个可以保存字节的位置获取字节或将字节存储到该位置。文件流就是一种后端流。文件流使用文件作为字节的后端存储,并读取或写入该文件。
文件流在 .NET 的 FileStream 类中实现,该类位于 System.IO 命名空间。FileStream 对象使用 ReadWrite 方法访问文件。将 FileStream 对象附加到现有文件时,您可以使用 Read 方法,以一系列字节的形式获取文件内容。而使用 Write 方法时,FileStream 对象可以将一系列字节写入文件(现有文件或新文件)。FileStream 类还使用 Seek 方法来定位文件中的特定位置。
后端流的其他示例有网络流(将数据放到 TCP/IP 堆栈或从中获取数据)和内存流(使用内存作为临时后端)。它们的基本结构与 FileStream 对象相同,都使用 ReadWrite 方法访问后端存储的字节。有些后端流(如网络流)不支持 Seek 方法,因为没有可供执行查找操作的永久存储内容。

过程流


过程流用于接收并处理字节,然后将字节写入其他流(通常是后端流)。例如,我们可以从名为 Stream 的 .NET 基类中继承,然后创建一个将大写字母更改为小写字母的过程流。再将这个流附加到任何后端流。现在,上图的关系可以表示为图 2。

图 2:使用流表示的读取文件、处理内容并写回结果的过程
我们的“变为小写”流类只在经常需要执行该操作时才有用。但这种流类可以对通过它的字节执行所需的各种操作。

.NET 中的加密


在 .NET 中,加密和解密是使用过程流来实现的。例如,加密的典型步骤为:
  1. 从某个输入流(例如,磁盘中的未加密文件)传入字节。
  2. 将字节送到加密流,加密流本身连接到某个输出流(例如,要保存加密数据的文件)。
  3. 加密流加密字节并自动将字节放到相关联的输出流中。

加密流被封装到一个名为 CryptoStream 的类(本文后面将详细介绍该类)。假设我们正在读取和写入磁盘文件,那么如果使用这一术语,则此过程的关系如图 3 所示。

图 3:加密文件的过程

加密类型


加密信息的方法已经有几百年的历史。小说家艾伦·坡就曾经涉足密码学,而设计和破解密码也曾经是第二次世界大战中的一项重要活动。然而,计算机的出现使密码学有了飞速发展。计算机强大的分析加密消息的能力迫使人们不断研究越来越难以破解的加密技术。
其结果是研究出了多种加密方法。.NET 中提供的常用方法包括:加密方法一般类型实现方法的 .NET 类数据加密标准 (DES)对称
(私钥)DESCryptoServiceProviderRC2 (RSA Data Security, Inc.)对称
(私钥)RC2CryptoServiceProviderRijndael对称
(私钥)RijndaelManagedTripleDES(在一行中使用三重 DES 加密)对称
(私钥)TripleDESCryptoServiceProvider数字签名算法非对称
(公钥)DSACryptoServiceProviderRSA(由 Rivest、Shamir 和 Adelman 发明,以他们名字的首字母命名)非对称
(公钥)RSACryptoServiceProvider
加密算法的一般类型有对称和非对称两种。对称算法使用相同的密钥来加密和解密数据。非对称算法使用一个公钥进行加密,而使用另一个密钥来解密。在本文最后,我们将继续介绍这一点。
如果只是使用加密方法,则不必详细了解其工作原理(谢天谢地,某些内容是相当复杂的);但如果要选择一种算法,则必须考虑以下三个主要因素:
  • 破解使用该算法加密的消息的难度
  • 算法的性能
  • 密钥的安全性

有许多 Web 站点讨论了以上因素。对于初学者,以下两个网站比较适合:http://www.microsoft.com/china/security/ 和 Snake Oil FAQ http://www.interhack.net/people/cmcurtin/snake-oil-faq.html(英文)。

使用 .NET 加密类


本表列出的 .NET 类都在 System.Security.Cryptography 命名空间中,因此使用它们时必须引用 System.Security.dll。此外,使用一对 IMPORTS 语句来引用命名空间会使代码更加简洁:
Imports System.SecurityImports System.Security.Cryptography

上表中的类都和一个名为 CryptoStream 的一般密码流类一起工作。这样便可以仅使用一个能实现多种加密的类来处理流操作。您甚至可以创建自己的加密类并将其插入 CryptoStream 中(尽管其安全性不太可能与上表列出的类相比)。
在我们的示例中,我们使用的加密方法可能是 .NET 中最简单的,即使用 DSE 算法进行对称密钥加密。实现 DES 的过程流被称为 DESCryptoServiceProvider。
大多数对称算法要求有两个单独的字节数组,用于加密过程。第一个是密钥。对于 DES,密钥是 8 个字节,而其他算法使用的字节数则不同。
必须将此私钥以一种安全的方式传递给解密文件的人,如果私钥泄露,加密信息也将泄露。但即使密钥不泄露,DES 加密也正常工作,这种加密方法也远远称不上是最安全的算法。
要加密一个信息块(通常是 8 个字节),需要同时使用密钥和上一个块的加密结果,也就是说,具有相同原始字符的块在加密后不会得出相同的结果。这样做的优点是,重复的块不会提供线索而使加密的破解变得更容易。
不过,第一个块没有前导块作为加密的输入。如果第一个块包含已知信息(如网络标头),则对第一个块实施反向工程,就会很容易地获得密钥。
为防止这种破解方法,DES 使用所谓的“初始化向量”。这是另一个字节数组,长度与密钥相同。将其与密钥一起使用,进一步加密 8 个字节的第一个块。还有其他几