CLR via C#第3章共享程序集和强命名程序集3.1 两种程序集,两种部署方式3.2 对程序集进行强命名
第2章讨论的主要是私有部署(private deployment),即程序集放在应用程序的根目录及其子目录中。以私有方式部署程序集可以在很大程度上控制程序集的命名、版本化和行为。
本章将讨论的是可以被多个应用程序共同访问的程序集,全局部署程序集(globally deployed assembly)。
两种程序集,两种部署方式
CLR支持两种程序集:弱命名程序集(weakly named assembly)和强命名程序集(strongly named assembly)。其中弱命名程序集这个术语是作者为了避免混淆而发明的,没有成为正式的叫法。
弱命名程序集和强命名程序集在结构上是相同的,它们使用同样的PE文件格式、PE表头、CLR表头、元数据、清单元数据表和IL。可以使用同样的工具,如C#编译器和AL.exe,来生成这两种程序集。它们的不同之处在于:强命名程序集有一个发布者的公共/私有密钥对,来唯一标识程序集的发布者。这个密钥对可以对程序集进行唯一标识、实施安全策略和版本策略,允许部署到用户机器的任何地方,甚至互联网。这种唯一标识使得应用程序在试图绑定一个强命名程序集时,CLR能够实施某些“已确知安全”的策略。
一个程序集的部署方式有两种:私有方式和全局方式。私有部署将程序集部署在应用程序根目录或某个子目录中。弱命名程序集只能以私有方式部署。全局部署将程序集部署在一些众所周知的地方,当CLR搜索程序集时会去这些地方查找。强命名程序集可以部署为私有方式或全局方式。
作者强烈建议:对所有你开发的程序集进行强命名。很可能以后版本的CLR将放弃弱命名程序集,而强制强命名。弱命名程序集会导致重名问题。此外,对程序集进行强命名可以唯一标识该程序集。如果CLR可以唯一标识程序集,就可以更好地应对版本化和向后兼容。事实上取消弱命名程序集更有助于理解CLR的版本策略。
但是实际上我们在用Visual Studio进行开发时,默认情况下生成的都是弱命名程序集。
对程序集进行强命名
强命名的程序集包含可以唯一标识程序集的四个特性:文件名(不含扩展名)、版本号、区域标识、公共密钥。由于公共密钥是很大的数字,我们通常使用一个来源于公共密钥的散列值。这个散列值称为公共密钥标记(public key token)。下面程序集的标识字符串(也叫程序集显示名称,assembly display name)标识了4个不同的程序集文件:
"MyTypes, Version=1.0.8123.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" "MyTypes, Version=1.0.8123.0, Culture="en-US", PublicKeyToken=b77a5c561934e089" "MyTypes, Version=2.0.1234.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" "MyTypes, Version=1.0.8123.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"微软选择了标准的公共/私有密钥对加密技术,而没有使用GUID、URL、URN。
如果一个公司希望唯一标识一个程序集,必须创建一个公共/私有密钥对,然后将公有密钥和程序集进行关联。不存在两个公司拥有相同公共/私有密钥对的情况。
一个弱命名程序集可以在清单元数据中嵌入版本号和区域特性,但CLR总是忽略版本号,并且只有在搜索卫星程序集时才会使用区域信息。因为弱命名程序集总是以私有方式部署,所以CLR在程序集根目录或子目录中搜索程序集时,只需要利用程序集的名称(加上.dll或.exe)就可以了。
要获得密钥需要使用强命名工具:SN.exe。SN的所有命令行开关都是大小写敏感的。生成公共/私有密钥对的命令如下:
SN –k MyCompany.snk该命令创建了一个名为MyCompany.snk的文件,该文件包含二进制格式的公共和私有密钥。
公共密钥非常庞大。可以使用SN工具来查看公共密钥。首先,使用-p开关,创建一个包含公共密钥的文件MyCompany.PublicKey:
SN –p MyCompany.snk MyCompany.PublicKey然后使用-tp开关:
SN –tp MyCompany.PublicKey得到的结果如下:
Microsoft (R) .NET Framework Strong Name Utility Version 4.0.20928.1 Copyright (c) Microsoft Corporation. All rights reserved. Public key is 00240000048000009400000006020000002400005253413100040000010001003f9d621b702111 850be453b92bd6a58c020eb7b804f75d67ab302047fc786ffa3797b669215afb4d814a6f294010 b233bac0b8c8098ba809855da256d964c0d07f16463d918d651a4846a62317328cac893626a550 69f21a125bc03193261176dd629eace6c90d36858de3fcb781bfc8b817936a567cad608ae672b6 1fb80eb0 Public key token is 3db32f38c8b42c9aSN.exe没有提供显示私有密钥的方法。
由于公共密钥较难使用,因此通常使用的都是公共密钥标识。公共密钥标记是一个64位的公共密钥散列值。
有了公共/私有密钥对,创建强命名程序集就容易了。在编译程序集时,使用/keyfile:<file>编译器开关:
csc /keyfile:MyCompany.snk Program.cs编译器看到这个开关时,打开snk文件,用私有密钥对程序集进行签名,并将公共密钥嵌入到清单中(嵌入到AssemblyDef表中,详细过程见下面的描述)。注意,你只能对包含清单的程序集文件进行签名,程序集的其他文件不能被显式签名。
如果使用Visual Studio,创建公共/私有密钥对的方法为:打开项目属性,选择Signing选项卡,选中Sign The Assembly复选框,选择<New…>选项,打开Create Strong Name Key对话框:
点击OK后,将在项目下创建一个pfx文件:
对签名过程的详细解释如下:当创建强命名程序集时,程序集的FileDef清单元数据表包含所有组成程序集的文件列表。当每个文件的名称添加到清单时,文件的内容被转换成散列值,该散列值和文件名一起也被存储在FileDef表中。要想改变默认的散列算法,可以使用AL.exe的/algid开关,或者在程序集的某个源代码文件中使用程序集级别的自定义特性System.Reflection.AssemblyAlgorithmIdAttribute。默认情况下使用SHA-1算法,这对绝大多数应用程序来说已经足够了。
在生成包含清单的PE文件之后,PE文件的全部内容(除了所有Authenticode Signature,程序集的强命名数据和PE头校验和)都被转换成一个散列值。这里的散列算法也为SHA-1并且不能改变。该散列值由发布者的私有密钥进行签名,生成的RSA数字签名存储在PE文件的一个保留区域(不包括在散列值的计算中)。CLR头和PE文件将被更新以反映数字签名已经嵌入文件中。
公共密钥还嵌入到了PE文件中的AssemblyDef清单元数据表。文件名、程序集版本号、区域性、公共密钥组成了程序集的强名称,这保证了程序集的唯一性。由于两个公司的公共/私有密钥对不同,因此他们不可能生成有着相同公共/私有密钥对的程序集。
这也就是前面所说过的:用私有密钥对程序集进行签名,并将公共密钥嵌入到清单中。
在编译源代码时,要为编译器指定所引用的程序集(/reference)。编译器的部分工作就是生成托管模块中的AssemblyRef元数据表。每个AssemblyRef元数据表条目都标识了一个被引用的程序集名称、版本号、区域、和公钥信息。
由于公有密钥过于庞大,如果一个程序集引用了多个程序集,文件将会变得很大。因此微软将公共密钥转换成散列值,并取其最后8个字节作为公共密钥标识,存储在AssemblyRef表中。一般来说,用户和开发者看到公共密钥标识的频率要远远高于完整的公共密钥值。但是,CLR不会用公共密钥标识制定安全策略或受信任的决策,因为概率上来说不同的公共密钥还是有可能产生相同的公共密钥标记的。
JeffTypes.dll文件的AssemblyRef元数据表信息如下:
AssemblyRef #1 (23000001) ------------------------------------------------------- Token: 0x23000001 Public Key or Token: b7 7a 5c 56 19 34 e0 89 Name: mscorlib Version: 4.0.0.0 Major Version: 0x00000004 Minor Version: 0x00000000 Build Number: 0x00000000 Revision Number: 0x00000000 Locale: <null> HashValue Blob: Flags: [none] (00000000)可以看出,JeffTypes.dll引用了一个有如下特性的程序集中的类:
"MSCorLib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"术语Locale应该为Culture。
而JeffTypes.dll的AssemblyDef元数据表如下:
Assembly ------------------------------------------------------- Token: 0x20000001 Name : JeffTypes Public Key : Hash Algorithm : 0x00008004 Version: 3.0.0.0 Major Version: 0x00000003 Minor Version: 0x00000000 Build Number: 0x00000000 Revision Number: 0x00000000 Locale: <null> Flags : [none] (00000000)这等同于:
"JeffTypes, Version=3.0.0.0, Culture=neutral, PublicKeyToken=null"PublicKeyToken为null,因为JeffTypes.dll为弱命名程序集。如果使用SN.exe来创建密钥文件,并使用/keyfile进行编译,程序集就将被签名。Public Key字段后面将会有值,程序集将被强命名。同时,AssemblyDef条目总是保存着完整的公共密钥,而不是公共密钥标记。完整的公共密钥确保了程序集不会被篡改。
CLR via C#第3章&共享程序集和强命名程序集&3.1 两种程序集,两种部署方式&3.2 对程序集进行强命名