.NET为我们提供了操作数字证书的两个主要的类,分为为:
System.Security.Cryptography.X509Certificates.X509Certificate2类, 每个这个类的实例可以表示一个证书;
System.Security.Cryptography.X509Certificates.X509Store类,可以对保存在计算机安全区域内的证书进行add/remove/get操作。
另外我们可以使用System.Security.Cryptography.X509Certificates.X509Certificate2UI类来显示证书消息的对话框,它就是在IE中的证书查看器的.NE实现。
生成证书
在介绍以上类的使用方法之前,我们先要拥有一个数字证书,获取数字证书有三种方法,一是从CA机构申请,二是自己搭建服务器发布证书,三是使用makecert.exe来生成一个证书文件。这里我们使用makecert.exe来生成一个证书文件,用来测试。启动VS2010的命令行,输入对应参数,生成名为TestCertificates的证书文件。如图
生成证书
makecert.exe的参数读者可以查看帮助,这里只解释图6-28中的参数。
参数说明:
-sr CurrentUser:指定主题的证书存储位置。Location 可以是 currentuser(默认值)或 localmachine
-ss MyTestContainer:指定主题的证书存储名称,输出证书即存储在那里。
-n CN=TestCert:指定主题的证书名称。此名称必须符合 X.500 标准。最简单的方法是在双引号中指定此名称,并加上前缀 CN=;例如,"CN=myName"。
-sky exchange:指定颁发者的密钥类型,必须是 signature、exchange 或一个表示提供程序类型的整数。默认情况下,可传入 1 表示交换密钥,传入 2 表示签名密钥。
-pe:将所生成的私钥标记为可导出。这样可将私钥包括在证书中。
生成的密钥文件被保存在了我们指定的MyTestContainer中,但到哪去查看我们的证书呢?Windows没有给我们准备好直接的管理证书的入口,但我们可以在MMC控制台自行添加。
- 开始 运行 MMC,打开一个空的MMC控制台。
- 在控制台菜单,文件 添加/删除管理单元 添加按钮 选"证书" 添加 选"我的用户账户" 关闭 确定
- 在控制台菜单,文件 添加/删除管理单元 添加按钮 选"证书" 添加 选"计算机账户" 关闭 确定
如图6-29,我们可以查看两个账户的证书管理,在我的账户中可以看到MyTestContainer下的证书TestCert。
在MMC控制台查看和管理证书
当然我们也可以将证书文件保存为文件,如图
图:将证书保存为文件
打开E盘,可以看到生成的证书文件。如图
图生成的证书文件
将证书保存为文件时,我们有三种选择:
-
带有私钥的证书
由Public Key Cryptography Standards #12,PKCS#12标准定义,包含了公钥和私钥的二进制格式的证书形式,以pfx作为证书文件后缀名。
-
二进制编码的证书
证书中没有私钥,DER 编码二进制格式的证书文件,以cer作为证书文件后缀名。
-
Base64编码的证书
证书中没有私钥,BASE64 编码格式的证书文件,也是以cer作为证书文件后缀名。
右键单击本地的证书文件,我们可以看到安装选项,可以把该证书文件安装到证书存储区。也可在MMC的证书管理台上执行导出任务将存储区的证书导出为文件。这里就不再演示了,读者可以自行实践。
编程操作证书
我们可以通过编程的方式操作操作本地的证书文件和在存储区中的证书。我们以刚才保存在E盘的test.cer文件为例,讲解如何读取本地的证书文件,并将它添加到存储区中。先看代码清单6-17。
代码清单 6-17 操作本地证书文件
using System; using System.IO; using System.Security.Cryptography.X509Certificates; using NUnit.Framework; [TestFixture] public class OperCert { [Test] private byte[] ReadFile(string fileName) { FileStream f = new FileStream(fileName, FileMode.Open, FileAccess.Read); int size = (int) f.Length; byte[] data = new byte[size]; size = f.Read(data, 0, size); f.Close(); return data; } [Test] public void CertTest() { try { X509Certificate2 x509 = new X509Certificate2(); byte[] rawData = ReadFile(@"e: estc.cer"); x509.Import(rawData); Console.WriteLine("{0}Subject: {1}{0}", Environment.NewLine, x509.Subject); Console.WriteLine("{0}Issuer: {1}{0}", Environment.NewLine, x509.Issuer); Console.WriteLine("{0}Version: {1}{0}", Environment.NewLine, x509.Version); Console.WriteLine("{0}Valid Date: {1}{0}", Environment.NewLine, x509.NotBefore); Console.WriteLine("{0}Expiry Date: {1}{0}", Environment.NewLine, x509.NotAfter); Console.WriteLine("{0}Thumbprint: {1}{0}", Environment.NewLine, x509.Thumbprint); Console.WriteLine("{0}Serial Number: {1}{0}", Environment.NewLine, x509.SerialNumber); Console.WriteLine("{0}Friendly Name: {1}{0}", Environment.NewLine, x509.PublicKey.Oid.FriendlyName); Console.WriteLine("{0}Public Key Format: {1}{0}", Environment.NewLine, x509.PublicKey.EncodedKeyValue.Format(true)); Console.WriteLine("{0}Raw Data Length: {1}{0}", Environment.NewLine, x509.RawData.Length); Console.WriteLine("{0}Certificate to string: {1}{0}", Environment.NewLine, x509.ToString(true)); Console.WriteLine("{0}Certificate to XML String: {1}{0}", Environment.NewLine, x509.PublicKey.Key.ToXmlString(false)); X509Store store = new X509Store(); store.Open(OpenFlags.MaxAllowed); store.Add(x509); store.Close(); } catch (Exception e) { Console.WriteLine("Error:" + e.Message); } } }
输入了如下内容:
Subject: CN=Joe's-Software-Emporium
Issuer: CN=Root Agency
Version: 3
Valid Date: 2013-6-28 13:12:48
Expiry Date: 2040-1-1 7:59:59
Thumbprint: 269ED45BCC22ABB2E416B19E16CBC68E1045DCDD
Serial Number: D6D43B334428E0BD40E148EE641524C0
Friendly Name: RSA
Public Key Format: 30 81 89 02 81 81 00 b7 a3 d1 9a 73 a3 f3 b0 1d 72 1e 5b 67 d2 b9 a6 4c f2 9f 01 7c 9d 7d 81 a5 7f b8 b6 54 fe 29 53 91 0c 99 60 01 89 43 2d 61 9f 4e f6 49 5f 9e 66 51 e3 cc 7d 6e 45 73 65 ef 09 c1 37 71 5c 1a 64 06 b7 ac ef 9f 50 68 8c a9 48 43 d0 7c d0 c5 01 c3 77 9d e5 b1 d4 83 d9 15 83 27 f0 2b f0 00 9a e0 10 2c 9c 6f 97 77 f1 e2 d3 f8 68 17 1c c5 5e a6 ec 03 a4 5c df 93 5e 88 46 fa 5d f7 76 9d bd 02 03 01 00 01
Raw Data Length: 451
Certificate to string: [Version]
V3
[Subject]
CN=Joe's-Software-Emporium
Simple Name: Joe's-Software-Emporium
DNS Name: Joe's-Software-Emporium
[Issuer]
CN=Root Agency
Simple Name: Root Agency
DNS Name: Root Agency
[Serial Number]
D6D43B334428E0BD40E148EE641524C0
[Not Before]
2013-6-28 13:12:48
[Not After]
2040-1-1 7:59:59
[Thumbprint]
269ED45BCC22ABB2E416B19E16CBC68E1045DCDD
[Signature Algorithm]
sha1RSA(1.3.14.3.2.29)
[Public Key]
Algorithm: RSA
Length: 1024
Key Blob: 30 81 89 02 81 81 00 b7 a3 d1 9a 73 a3 f3 b0 1d 72 1e 5b 67 d2 b9 a6 4c f2 9f 01 7c 9d 7d 81 a5 7f b8 b6 54 fe 29 53 91 0c 99 60 01 89 43 2d 61 9f 4e f6 49 5f 9e 66 51 e3 cc 7d 6e 45 73 65 ef 09 c1 37 71 5c 1a 64 06 b7 ac ef 9f 50 68 8c a9 48 43 d0 7c d0 c5 01 c3 77 9d e5 b1 d4 83 d9 15 83 27 f0 2b f0 00 9a e0 10 2c 9c 6f 97 77 f1 e2 d3 f8 68 17 1c c5 5e a6 ec 03 a4 5c df 93 5e 88 46 fa 5d f7 76 9d bd 02 03 01 00 01
Parameters: 05 00
[Extensions]
* 颁发机构密钥标识符(2.5.29.1):
KeyID=12 e4 09 2d 06 1d 1d 4f 00 8d 61 21 dc 16 64 63
Certificate Issuer:
CN=Root Agency
Certificate SerialNumber=06 37 6c 00 aa 00 64 8a 11 cf b8 d4 aa 5c 35 f4
Certificate to XML String: <RSAKeyValue><Modulus>t6PRmnOj87Adch5bZ9K5pkzynwF8nX2BpX+4tlT+KVORDJlgAYlDLWGfTvZJX55mUePMfW5Fc2XvCcE3cVwaZAa3rO+fUGiMqUhD0HzQxQHDd53lsdSD2RWDJ/Ar8ACa4BAsnG+Xd/Hi0/hoFxzFXqbsA6Rc35NeiEb6Xfd2nb0=</Modulus><Exponent>AQAB</Exponent></RSAKeyValue>
代码清单6-17演示了如何读取本地证书文件的方法。静态方法ReadFile用来从本地磁盘中读取证书文件到byte数组中。主要的操作都在Main方法中。X509Certificate2 x509 = new X509Certificate2()一句使用无参数的构造函数初始化X509Certificate2类的实例x509。然后我们使用x509.Import(rawData)语句将byte数组导入到当前证书实例。接下来是输出该证书的信息。
输出信息之后,我们看下面的四行代码:
X509Store store = new X509Store(); store.Open(OpenFlags.MaxAllowed); store.Add(x509); store.Close();
首先我们初始化一个X509Store类的实例store,然后使用Open方法打开存储区,添加上面读取的证书到存储区。
X509Certificate2一共提供了14个构造函数供我们使用,这里就不一一介绍了。我们也可以通过X509Certificate2类的构造函数直接导入本地的证书文件,可以使用代码清单6-18所示的方式。
代码清单6-18 使用构造函数导入证书文件
X509Certificate2 myX509Certificate2 = new X509Certificate2( @"e:MyTestCert.pfx", //证书路径 "password", //证书的私钥保护密码 X509KeyStorageFlags.Exportable //表示此证书的私钥以后还可以导出 );
代码清单6-18给出了如何导入带私钥保护密码的证书的方法。X509KeyStorageFlags 枚举用来标识X.509 证书的私钥导出到何处以及如何导出。该枚举的成员说明如表6-1所示。
表6-1 X509KeyStorageFlags 枚举说明
|
说明 |
DefaultKeySet |
使用默认的密钥集。用户密钥集通常为默认值。 |
UserKeySet |
私钥存储在当前用户存储区而不是本地计算机存储区。既使证书指定密钥应存储在本地计算机存储区,私钥也会存储到当前用户存储区。 |
MachineKeySet |
私钥存储在本地计算机存储区而不是当前用户存储区。 |
Exportable |
导入的密钥被标记为可导出。 |
UserProtected |
通过对话框或其他方法,通知用户密钥被访问。使用的加密服务提供程序 (CSP) 定义确切的行为。 |
PersistKeySet |
导入证书时会保存与 PFX 文件关联的密钥。 |
那么如何操作存储区中的证书呢,可以使用代码清单6-19的方式。
代码清单6-19 操作存储区中的证书
X509Store store = new X509Store(StoreName.My, StoreLocation.CurrentUser); store.Open(OpenFlags.ReadOnly); //轮询存储区中的所有证书 foreach(X509Certificate2 myX509Certificate2 in store.Certificates) { //将证书的名称跟要导出的证书MyTestCert比较,找到要导出的证书 if (myX509Certificate2.Subject == "CN=TestCert") { //证书导出到byte[]中,password为私钥保护密码 byte[] CertByte = myX509Certificate2.Export(X509ContentType.Pfx,"password"); //将证书的字节流写入到证书文件 FileStream fStream = new FileStream( @"C:SamplesPartnerAEncryptMsgMyTestCert_Exp.pfx", FileMode.Create, FileAccess.Write); fStream.Write(CertByte, 0, CertByte.Length); fStream.Close(); } } store.Close();
代码清单6-19首先声明X509Store类的实例store,使用了两个参数的构造函数,第一个参数是存储容器的名称,StoreName枚举只能枚举系统默认的存储区名称。第二个参数是StoreLocation枚举,用来标识是本机证书还是当前用户证书。导出容器证书使用的是Export方法。第一个参数X509ContentType.Pfx表示要导出为含有私钥的pfx证书形式,第二个参数为私钥保护密码。如果要导出为不含私钥的cer证书,第一个参数使用X509ContentType.Cert,表示导出为不含私钥的cer证书,也就不需要密码了。
创建发行者证书
发行者证书是验证发行者可靠性的证书文件,保护证书发行者的签名。我们可以从证书颁发机构获得该文件。做为程序测试,我们可以使用Cert2spc.exe来生成发行者证书。从命令行启动该程序,如图6-32所示。
图6-32 生成SPC文件
如图6-32,我们使用Cert2spc.exe以test.cer为参数生成目标为tset.spc的发行者证书,如果存在多个证书文件,可以作为参数以空格隔开生成统一的发行者证书。
使用证书对文件签名
签名工具 (SignTool.exe) 是一个命令行工具,用于对文件进行数字签名,验证文件或时间戳文件中的签名。我们可以对cab文件、dll文件或者其他文件进行签名,从互联网访问这些文件的时候就需要安装和验证证书。该工具详细的说明读者可以从MSDN上找到,我就不在重复了。