代码改变世界

CLR via C# 边读边想 03 - 本地程序集和强命名程序集

2012-06-25 20:38  richardzhaoxb  阅读(215)  评论(0编辑  收藏  举报

Two Kinds of Assemblies, Two Kinds of Deployment 

CLR 支持两种类型的程序集:weakly named assemblies and strongly named assemblies。这两种类型的Assembly在文件内部结构上没有什么区别,都是按照前面两章介绍的由 PE32(+) header,CLR header,metadata,manifest table,还有 IL 组成。而且也是用同样的工具来生成程序集,例如 CSC.exe 和 AL.exe 。

他们最大的区别就是强命名的程序集会由发布者的一对key来为其签名,而这对key可以唯一的标识这个发布者。这对key也让程序集成为了唯一标识的、安全的、有明确版本的程序集。

程序集有两种部署的方式:本地的和全局的。本地部署就是将程序集拷贝到程序的目录或子目录。weakly named assemblies 只能被本地部署。而 strongly named assemblies 既可以被本地部署也可以被全局部署。

注意:强烈建议使用 strongly named assemblies,很有可能在以后的 CLR 中要求所有的 assembly 都是 请强命名的。 weakly named assembly 将不再被鼓励使用。

 

Giving an Assembly a Strong Name

一个强命名由四部分组成:

  1. 文件名,不带扩展名的。
  2. 版本号,在 manifest table 中定义
  3. 文化表示,在 manifest table 中定义
  4. 公钥。 由于一个公钥通常是一个非常大的数字,所以一般使用从这个公钥经过一个哈希算法得到一个 public key token。

那么如何确保区分不同公司发布的 assembly 是不同的呢? 微软选择的是标准的 公钥/私钥 加密技术,这对key可以确保两个公司就算发布了一样名字和版本的程序集,也不会产生混淆。 

在上一章节中,我们知道可以给程序集设置版本号和culture,这些信息存储在 manifest metadata 中。然而,对于 weakly name assembly 来说,CLR 会忽略 version number,只关注 assembly name。

创建一个强命名的程序集的第一步是用 Strong Name 工具获得一个key,用下面的命令行可以产生一对key:

SN –k MyCompany.snk 

注意: SN.exe 命令的选项是区分大小写的。

上面的命令产生了一个名叫 MyCompany.snk 的文件,它包含了一对以二进制存在的公钥和私钥的 key number。

公钥 key number 是非常大的一个数,如果你想看看这个公钥 key number,用下面的方法:

第一步,把公钥单独生成一个文件:

SN –p MyCompany.snk MyCompany.PublicKey

第二步,打印出公钥 key number:

SN –tp MyCompany.PublicKey

SN.exe 工具不提供任何方法显示私钥。

由于公钥太大,不方便使用,所以使用更加简单的 public key token ,这个token是从 public key 得到的一个 64-bit 的哈希值的最后8个字节。

在build程序时使用强命名的方法:

csc /keyfile:MyCompany.snk Program.cs

上面的命令,编译器会使用 MyCompany.snk 中的 private key 来给程序集签名,而后把 public key 签入到 manifest 中,注意,你只给包含 manifest 的文件签名,其他的文件没有被签名。

默认情况下,使用的是SHA-1算法,你可以用 AL.exe’s /algid 选项来切换算法。 但是SHA-1算法已经满足了绝大多数的应用。

签名后的结构如下图,PE的所有内容都被hash之后,又被发布者的 private key 签名,得到一个 RSA 的数字签名,这个数字签名又被签入到 PE 文件中,CLR header 会被更新记录下 数字签名在 PE 文件中的位置。

由 file name, the assembly version, the culture, and the public key 这四部分组成的强命名,让程序集成为唯一的,不用担心不同公司会产生相同强命名的程序集。除非这个公司把他的公钥/私钥对和别人共享了。

 

The GAC (Global Assembly Cache)

之前介绍了如何创建强命名的程序集,现在来看看如何部署强命名的程序集,以及CLR如何找到和加载它。 

当CLR要加载一个强命名的程序集时,它会到GAC中去找。 GAC 的地址在 C:\Windows\Assembly

GAC 目录中包含有很多子目录, 有一个特殊的规则来命名这些子目录。所以千万不要自己往这个目录/子目录下拷贝文件,而是应该用专门的工具(gacutil.exe)来做部署。

/i 选项用来向GAC中安装。/u 选项用来从GAC中卸载。

注意:运行 gacutil.exe 需要 Windows Administrator group 的权限。

 

Building an Assembly That References a Strongly Named Assembly

不论你开发任何程序,你都会引用到强命名的程序集,因为System.Object定义在MSCorLib.dll中,这个dll 是强命名的。

在使用 CSC.exe 时,如果在 /reference 后面跟的是一个全路径,编译器会加载一个 local assembly,如果不是一个全路径,编译器会按照顺序依次到下面的几个目录寻找:

  1. working directory,当前目录
  2. csc.exe 所在的目录
  3.  /lib 的选项后面指定的路径,如果有 /lib 选项的话。
  4. LIB环境变量指定的路径,如果有 LIB 环境变量的话。

举个例子,如果你在编译时加了一个选项:/reference:System.Drawing.dll,编译器会到上述的几个目录去找这个dll,会在第2点,csc.exe 所在目录找到。但是要特别注意,虽然编译时引用的是 csc.exe 目录下的文件,但是在 runtime ,引用的却不是这个目录下的文件。

你可以发现,安装了.Net Framework 后,微软只带的 Assembly 分别在安装目录和GAC中拷贝了一份,安装目录中主要用来编译时被引用,而GAC中的是在 runtime 被引用。

 

Strongly Named Assemblies Are Tamper-Resistant

强命名如何能起到防篡改的功能呢? 当一个 assembly 被安装到 GAC,系统把包含 manifest 的文件取 hash 值,并和嵌入在 PE 中的 RSA 数字签名比较,如果相同说明是没有被篡改过的,如果不同,说明这个 assembly 被发布者之外的人篡改过,也就不能被安装到GAC中。

 

Delayed Signing 

如果你要发布强命名的程序集,你需要用到私钥来签名。然而,在开发和测试时,你不能方便的访问到私钥,正应为如此,微软支持延迟签名,也被称为部分签名。延迟签名就是允许你先用公钥签名。由于可以访问到公钥,那么其他引用这个延迟签名的assembly的其他程序集就可以嵌入一个正确的public key在 AssemblyRef manifest中。延迟签名的程序集也可以放在GAC中。 但是不能防止被篡改。

使用 /delaysign 选项告诉编译器做延迟签名。使用 延迟签名做开发和部署的整个过程如下:

1.先获得public key,然后使用下面的命令行

csc /keyfile:MyCompany.PublicKey /delaysign MyAssembly.cs

2.运行下面的命令行,让CLR不要对程序集的内容做hash和比较,这样可以允许把延迟签名的程序集按照到GAC。这样你就可以对你的程序集进行测试。

SN.exe –Vr MyAssembly.dll

3.当准备好要发布时,获取公司的私钥, 执行下面的命令。

SN.exe -R MyAssembly.dll MyCompany.PrivateKey

但是在安装到GAC前,请先执行下面的步骤。

4.把 verification 在开启,使用下面的命令:

SN –Vu MyAssembly.dll 

 

Privately Deploying Strongly Named Assemblies

虽然强命名的程序集可以被安装到GAC,但是事实上,我们只有在程序集会被多个App引用时才有必要把它放到GAC中。