使用 .NET Framework 的 Cryptography 命名空间保护私有数据
Dan Fox
本文假定您对 C# 和 Crypto API 已经很熟悉。(本文包含一些指向英文站点的链接。)
摘要:.NET Framework 包括一组加密服务,这组服务大大扩展了 Windows 通过 Crypto API 提供的服务。在本文中,作者探讨了 System.Security.Cryptography 命名空间和用于应用加密转换的编程模型。他讨论了 .NET 中的密码比以前更容易使用的原因,包括开发人员对密码 API 拥有更方便的编程访问方式以及对称算法和不对称算法之间的差别。接着,对使用最广泛的一些算法(包括 RSA、DSA、Rijndael、SHA 和其他散列算法)进行了简单的讨论。
电子商务的爆炸性增长需要保护更多的保密数据,对安全算法的需要也日益增加。Microsoft® 在 1996 年通过引入 Cryptography API (Crypto API) 首次涉及了数据保密性问题。今天,安全保护的持续进步正推动着加密服务的不断发展,例如 Microsoft .NET 公共语言运行库 (CLR) 的 System.Security.Cryptography 命名空间。
此命名空间允许通过编程访问各种加密服务,您可以将这些服务并入应用程序来加密和解密数据、确保数据完整性、处理数字签名和证书。一些服务也可用在框架的其他方面,例如 ASP.NET 身份验证。本文将简要介绍 .NET Framework 中提供的加密服务的基本原理,并用一些代码示例来说明如何在应用程序中使用这些类。
Cryptography 命名空间
在最高级别上,Cryptography 命名空间可以分成四个主要部分(请参见图 1)。Cryptography 命名空间的主要功能是提供用来实现算法(例如加密和创建散列)的类。这些算法是使用包含两级继承的可扩展方式来实现的。抽象基类(例如 AsymmetricAlgorithm 或 HashAlgorithm)位于层次结构的顶端并表示算法类型。然后从顶级类派生出第二级抽象类,为算法提供公开的接口。例如,SHA1(安全散列算法)类是从 HashAlgorithm 派生的,其中包含 SHA1 算法特有的方法和属性。最后,从第二级抽象类派生出算法的实现,这是大部分客户端应用程序实例化并使用的算法。在这个具体级别上,这些实现可以是托管的、非托管的或两者兼具。
非托管的实现通常带有后缀“CryptoServiceProvider”(例如,SHA1CryptoServiceProvider),表示该实现实际上是由加密服务提供程序 (CSP) 提供的。CSP 安装在操作系统层,起了 Crypto API 包装程序的作用。托管的实现带有后缀“Managed”(例如 SHA1Managed),这些实现不依赖于 Crypto API,因此完全是由托管代码实现的。
关于这些算法还需要注意的是,当这些具体的类被实例化时,默认构造函数总是导入该算法的具有合理、安全值的默认参数(如果可能)。例如,依赖于公钥密码的不对称算法将生成随机密钥对,而对称算法则生成一个随机密钥和一个初始化矢量 (IV) 并自动设置诸如 Mode 和 Padding 等属性。此外,如果有可能,这些算法将使用“strong”默认值。
System.Security.Cryptography 命名空间的第二个主要类集合包括数据加密和解密进程中使用的类,以及各种 helper 类。命名空间中包含的类包括 RandomNumberGenerator 的抽象类(从中派生出 RNGCryptoServiceProvider),以及 ToBase64Transform 和 FromBase64Transform 类(用于与 base64 之间来回转换数据)。
除了提供加密算法以外,Cryptography 命名空间还包含子命名空间 X509Certificates。其中只包含三个用于表示和管理 Authenticode X.509 v.3 证书的类。X509Certificate 类提供静态方法 CreateFromCertFile 和 CreateFromSignedFile,用于创建证书的实例:
Dim c As X509Certificate c = X509Certificate.CreateFromCertFile("myCert.cer") Console.WriteLine(c.GetName) Console.WriteLine(c.GetPublicKeyString) Console.WriteLine(c.GetSerialNumberString) Console.WriteLine(c.GetExpirationDateString)然后证书可被用于多个目的,包括验证 Web 服务器,其实现方法是通过 System.Net.HttpWebRequest 对象的 ClientCertificates 属性(该属性提供一个 X509CertificateCollection 对象)将该服务器连接到一个客户端请求。
Cryptography 命名空间还包括子 XML 命名空间,.NET Framework 的安全系统使用该空间对 XML 对象进行数字化标记。该框架附属于有关 XML 签名语法和处理的 W3C 规范草案 (http://www.w3.org/TR/2000/WD-xmldsig-core-20000228/)。该规范包括用于创建和表示数字签名的 XML 语法和处理规则,可以应用于包含该签名的 XML 文档的任何内部或外部内容。尽管该规范目前还没有强调 XML 的加密,但为 XML 文档提供完整性、消息验证和签名者身份验证服务无疑是重要的。
本文的余下部分将集中讨论加密算法和用于加密转换的编程模型。
加密算法
正如图 1 所示,Cryptography 命名空间中的一种加密算法类型是对称加密算法。之所以命名为对称算法,是因为该算法的加密和解密使用同一个密钥。很显然,发送者和接收者都必须对该密钥进行保密以保证加密的安全性。另外,当处于 CBC 模式时,对称算法需要一个初始化矢量,用于初始化该算法和引入附加的加密变量的非秘密二进制值。SymmetricAlgorithm 类是抽象基类,其他特定算法的类都是从它派生的。
支持的对称算法包括 Data Encryption Standard (DES)、RC2、Rijndael 和 Triple Data Encryption Standard (TripleDES)。每个算法都包含从 SymmetricAlgorithm 派生的抽象基类,例如 DES。也包含从基类(例如 DESCryptoServiceProvider)派生的服务提供程序类或托管类。这个类包含用于加密和解密数据的方法。对称算法的层次结构请参见图 2。
图 2:对称算法
具体类(例如 RijndaelManaged)如图 2 所示,可以被实例化且其属性可被调用(请参见图 3)。图 3 中创建了一个新的算法实例,可以自动生成密钥值和作为字节数组的初始化矢量,串行化后变成字符串值并打印到控制台。请注意,属性(例如 KeySize 和 BlockSize)用来决定密钥的长度,以及一次操作中可以加密或解密的数据量(以位为单位)。
第二种类型是公钥算法或不对称算法;该算法是从抽象基类 AsymmetricAlgorithm 派生的。这种类型的算法中包括著名的 Digital Signature Algorithm (DSA) 和 RSA(以该算法的创建者 Ron Rivest、Adi Shamir 和 Len Adleman 命名)算法。不对称算法依赖于一对密钥,一个私有而另一个公开。一般来说,任何人均可以使用公钥,发送者使用公钥来加密数据,而私钥则要保证安全并用于解密那些使用公钥加密的数据。使用 RSA 时经常采用这种方式以保证数据安全。
这些算法最终是从抽象类 DSA 和 RSA 派生的,而后两者则是从 AsymmetricAlgorithm 派生的,如图 4 所示。此外,这些算法的复杂本质意味着附加的 helper 类(例如从 AsymmetricKeyExchangeFormatter 和 AsymmetricSignatureFormatter 派生的类)是必需的。
图 4:不对称算法
除了允许不对称算法的默认构造函数生成密钥对外,您也可以在 CSP 维护的存储区中检索现有的密钥对。为此,请用 Crypto API 密钥存储区中现有密钥对的密钥容器名实例化该不对称算法,办法是将密钥容器名导入 CspParameters 对象并将此实例提供给不对称算法的构造函数。有关如何存储和检索不对称密钥对的示例,请访问 http://www.gotdotnet.com/team/clr/about_security.aspx。
Cryptography 命名空间公开的最后一种算法类型是散列算法。该算法基于一个较长的字节序列来计算固定大小的二进制值(称为简要)的唯一序列。可以使用散列算法来确定数据是否被篡改。当您将简要与数据一起发送时,收件人可以重新计算该简要。如果输入的数据不同,那么散列算法的输出也就不同,因此通过对比新简要与旧简要就可以指出数据是否已经更改。散列算法通常用作数字签名的基础。
Cryptography 命名空间中包含名为 HashAlgorithm 的基类,以及支持 MD5、SHA1、SHA256、SHA384 和 SHA512 算法的派生类。MD5 算法生成 128 位散列,而 SHA1 生成 160 位散列。与其他版本 SHA 相关联的数字表示它们散列的长度。散列越长,算法越安全,同时也越难以强制使其中断。这些算法都有非托管版本和托管版本,如图 5 所示。
图 5:散列算法
要计算简要,您只需实例化散列算法并调用其从 HashAlgorithm 继承并重载的 ComputeHash 方法,如下所示:
Dim fsData As New FileStream("mydata.txt", _ FileMode.Open, FileAccess.Read) Dim digest() As Byte Dim oSHA As New SHA512Managed() digest = oSHA.ComputeHash(fsData) fsKey.Close()其中,向 ComputeHash 方法传递了一个 Stream 对象,但该方法也可以接受字节数组参数。
Cryptography 命名空间还包括名为 KeyedHashAlgorithm 的抽象类(请参见图 5)。在 HMACSHA1 和 MACTripleDES 类中实现的算法是从 KeyedHashAlgorithm 派生的,用来生成消息验证代码 (MAC)。只要发送者和接收者共享一个密钥,MAC 就可用于确定通过不安全信道发送的数据是否被篡改。
尽管抽象基类 SymmetricAlgorithm、AsymmetricAlgorithm、HashAlgorithm 和 KeyedHashAlgorithm 不能直接由客户端实例化,但每一个基类都公开了一个名为 Create 的重载静态(共享)方法。可以不带参数调用该方法来创建某个算法的实例,默认的对称算法的实例为 RijndaelManaged,不对称算法的实例为 RSACryptoServiceProvider,散列算法的实例为 SHA1CryptoServiceProvider,固定散列算法的实例为 HMACSHA1。第二版也接受映射到该实现的字符串(可以参阅 .NET Framework 联机帮助中的映射列表)。每个算法的抽象类(例如 SHA)也以完全相同的方式包含一个重载的静态 Create 方法,该方法实例化该特定算法的默认实现或接受字符串标识符。
在任一情况下,对象都是通过调用 CryptoConfig 类的 CreateFromName 方法并传递给它一个字符串标识符来创建的。这个映射列表处理在调用 Create 方法时所使用的默认值。另外,该列表也可以将传递给 Create 方法的字符串映射到特定的类。例如,当不带参数调用时,Create 方法将默认字符串“System.Security.Cryptography.HashAlgorithm”映射到 SHA1CryptoServiceProvider 类。当传递的字符串为“SHA1”时,该方法映射相同的类。
为了说明这些方法,请考虑以下代码,其中每一条语句与其他语句都是等价的,都会创建一个 RijndaelManaged 类的实例:
Dim r, r1, r2, r3 As RijndaelManaged r3 = CType(CryptoConfig.CreateFromName("Rijndael"), "_ RijndaelManaged) r2 = CType(SymmetricAlgorithm.Create, RijndaelManaged) r1 = CType(Rijndael.Create, RijndaelManaged) r = New RijndaelManaged()看上去可能很迷惑人,但这些类就是这样设计的,这是为了推迟到运行时再决定使用哪个算法,或使用配置系统来指定。事实上,整个系统的配置文件 (machine.config) 可以使用 cryptoNameMapping 中的节来扩充,该节可用来替代默认映射或被添加到 Mscorlib.dll 硬编码的映射列表中。例如,图 6 中所示的 XML 可以被添加到 machine.config 文件的配置节中,以便为 MD5CryptoServiceProvider 设置默认的散列实现并为该实现创建新的字符串“dan”映射。
如何创建自己的映射?您可以添加 cryptoClasses 元素中的 cryptoClass 元素来定义引用的名称(本例中为“myMD5”),并详细说明该类和程序集,然后使用 nameEntry 元素来创建映射。这样,客户端代码即如下所示:
Dim m, m1 As MD5CryptoServiceProvider m = CType(HashAlgorithm.Create, MD5CryptoServiceProvider) m1 = CType(CryptoConfig.CreateFromName("dan"), "_ MD5CryptoServiceProvider)在两个实例中,都创建了 MD5CryptoServiceProvider 类型的对象。
使用加密服务
基于流的编程模型遍布于 .NET Framework 中。从 System.IO.Stream 派生的流类用来表示存储区中的数据(例如文本文件、XML 文档、MSMQ 消息、内存和网络),也可交替地用来将数据移入或移出这些存储区。Cryptography 命名空间产生这一概念就是一件很自然的事,因为需要提供一种优良而高效的方式,通过使用上面所讨论的算法来加密和解密数据。该功能的核心是 CryptoStream 类,该类由 System.IO.Stream 所派生并用作基于流的加密转换的模型。
为了说明如何使用该命名空间中基本的加密功能,图 7 中显示了简单的 TextFileCrypto 类。使用具有由该类生成并保存到文件中的密钥的 DES 算法,该类可用于对称地加密和解密文件。
就象您所见到的,该类的构造函数实例化一个类级的 DESCryptoServiceProvider 类,而该类提供 CreateEncryptor 和 CreateDecryptor 方法。这些方法以后被用于返回执行加密和解密的对象。公开的 KeyFile 和 FileName 属性用于打开现有的包含密钥和初始化矢量的文件,并设置要加密或解密的文件的名称。如果存在密钥文件,那么需要使用私有的 OpenKeyFile 方法打开该文件,并将该密钥和初始化矢量读入字节数组中。SaveKeyFile 方法调用基础 DES 类的 GenerateKey 和 GenerateIV 方法来生成随机密钥和初始化矢量。这些值随即保存到作为参数传递来的文件中。此时,密钥文件应该保密并可由将要使用该密钥来解密数据的用户保存。
应该注意 DES 算法使用 56 位(7 字节)的密钥。DES 只支持一种密钥长度,有关其他算法的有效密钥长度可以查询 LegalKeySizes 属性,该属性返回 KeySize 对象的数组,这些对象用属性 MinSize、MaxSize 和 SkipSize(递增)来表示有效的密钥长度。例如,Rijndael 算法支持 128、192 和 256 位的密钥长度。对于增强加密,可以使用 KeySize 属性来设置新的密钥长度。
EncryptFile 和 DecryptFile 方法是 TextFileCrypto 类的核心。EncryptFile 方法所做的第一件事是打开要加密的文件和一个用于存储加密后数据的临时文件。然后使用服务提供程序(本例中为 DESCryptoServiceProvider)的 CreateEncryptor 方法创建一个加密器对象。该对象实现 ICryptoTransform 接口,因此可以被传递给构造函数 CryptoStream,该构造函数在数据被写出时将数据转换成 FileStream(本例中为 fsOutput)支持的文件。然后原文件被临时文件覆盖。结果是生成了一个加密文件,可以使用 FTP 或作为电子邮件的附件通过 Internet 安全发送。
DecryptFile 正好相反,该方法使用 CreateDecryptor 方法创建一个解密器对象。该对象同样被传递给 CryptoStream 的构造函数,目的是当从 FileStream fsRead 中读该文件时解密该文件。最后,解密后的数据由 StreamWriter 类使用 StreamReader 类的 ReadToEnd 方法保存在文本文件中。
如下所示,客户端可以使用 TextFileCrypto 类,用 4 行简单的代码来加密数据:
Dim objCrypt As New TextFileCrypto() objCrypt.FileName = "students.txt" ' 生成并保存密钥 objCrypt.SaveKeyFile("mykey.dat") ' 加密文件 objCrypt.EncryptFile()当另一个客户端收到该文件时,可以用同样简单的方式解密该文件:
Dim objCrypt As New TextFileCrypto() objCrypt.FileName = "students.txt" ' 读入密钥 objCrypt.KeyFile = "mykey.dat" ' 解密文件 objCrypt.DecryptFile()Windows 中的增强加密
尽管解除了对增强加密产品出口的旧有法律限制,但如果您的操作系统是出口版本,.NET Framework 仍然不会为非托管实现使用增强加密。为了规避这种情况,您可以安装 High Encryption Pack。Windows 2000 的 Service Pack 2 中包括了 High Encryption Pack,您可以从 Windows 2000 High Encryption Pack 中获得这个 Service Pack。
对于 Windows NT® 4.0,Service Pack 以标准和高加密两种版本发行。如果您还没有安装任一版本,请下载 Service Pack 6a 的高加密版。
对于 Windows ME、Windows 98 和 Windows 95,Microsoft Internet Explorer 5.5 中包括了 High Encryption Pack。如果您运行的 Internet Explorer 版本低于 5.5,可以获得相应的 High Encryption Pack。
有关相关文章,请参阅:
Geek Speak Decoded #9: Cryptography and Encryption
A Simple Guide to Cryptography
有关背景信息,请参阅:
http://www.gotdotnet.com/team/clr/about_security.aspx
http://www.gotdotnet.com/team/clr/cryptofaq.htm