生成强名称程序集
通过嵌入公钥并使用私钥签名,可以生成强名称(strong name)的程序集。强名称程序集由4部分进行标识:名称、版本、区域性和公钥。与之相对的,我们可以把没有嵌入公钥和使用私钥签名的程序集称之为弱名称(weak name)程序集(这个术语是Jffery Richter创造的)。强名称程序集与弱名称程序集相比,有以下特点:
* 强名称程序集可以保证唯一性。公/私密钥对是由发行者自行生成的,是唯一的,保证了程序集的标识不会重复。
* 强名称程序集可以防篡改。强类型程序集使用私钥对自己进行了签名,这样在被加载时可以检查程序集是否被修改。
* 强名称程序集可以实施版本策略。对于弱名称程序集,引用它的程序不会关心它的版本,而对于强类型的程序集来说,引用它的程序会被绑定到特定版本的程序集上,如果使用新版本的强名称程序集替换旧版本,会导致程序无法运行。(当然还可以使用配置文件对强名称程序集进行重定向)。
* 强名称程序集可以部署到GAC中。GAC指全局程序集缓存,这是一个公共目录,放在此处的程序集可以被本机任意一个程序所引用。弱名称程序集无法部署到此处。不同版本的相同程序集还可以同时存在于GAC中。
* 强名称程序集只能引用强名称程序集。弱名称程序集可以引用强名称程序集,也可以引用弱名称程序集,但强名称程序集只能引用强名称程序集。
* 强名称程序集支持并行执行。并行(side-by-side)执行是指程序同时引用了多个版本的同名程序集,这样在运行时,会有多个版本的同名程序集被加载和同时执行。通常不建议使用。
下面来研究一下如何生成强名称的程序集。首先,使用SN.exe创建一个密钥文件:
sn.exe -k MyKey.snk
生成的文件包含了公钥和私钥的内容。我们可以查看公钥的内容,私钥是不允许查看的,所以要先将公钥提取出来。仍然是使用SN.exe:
sn -p MyKey.snk MyPublicKey.snk
sn -tp MyPublicKey.snk
前一个命令将密钥文件中的公钥提取出来,放到 MyPublicKey.snk 文件中;后一个命令用于显示该文件中的公钥和公钥标记(Public key token),显示的内容可能如下(每个人生成的都不同):
Microsoft (R) .NET Framework Strong Name Utility Version 3.5.21022.8
Copyright (c) Microsoft Corporation. All rights reserved.
Public key is
0024000004800000940000000602000000240000525341310004000001000100757c8b7854ffcb
4763250746c094e45db0c715214415fb01bd178f3374224c1292dbbc9dddfb6af7de1766888464
1a39fbea9d0bee001c093b228400aa39c0db5724fc11c221bd2c7442a30ef26c076b1bb0f559ce
7955572b4174125494a593c199d968019323483e72d5bdb93d96af14ccfeb0c5d4af6ea191d226
e6812db5
Public key token is 337642649f453c2c
公钥标记是公钥的64位散列值,用于简化对公钥的引用。
第二步是创建强名称程序集。我们可以在源文件中使用AssemblyKeyFileAttribute,但在编译时会产生警告,建议使用命令行选项来代替此特性。所以此处使用csc.exe:
csc /t:library /keyfile:MyKey.snk MyType.cs
运行后得到 MyType.dll ,我们可以显示其中包含的公钥标记,看是否和上面的相同:
sn -Tp MyType.dll
显示内容如下:
Microsoft (R) .NET Framework Strong Name Utility Version 3.5.21022.8
Copyright (c) Microsoft Corporation. All rights reserved.
Public key is
0024000004800000940000000602000000240000525341310004000001000100757c8b7854ffcb
4763250746c094e45db0c715214415fb01bd178f3374224c1292dbbc9dddfb6af7de1766888464
1a39fbea9d0bee001c093b228400aa39c0db5724fc11c221bd2c7442a30ef26c076b1bb0f559ce
7955572b4174125494a593c199d968019323483e72d5bdb93d96af14ccfeb0c5d4af6ea191d226
e6812db5
Public key token is 337642649f453c2c
由此可见,公钥的内容确实嵌入到了程序集当中。除此之外,程序集的全部内容经过散列编码后,还使用密钥进行了签名,也嵌入到了程序集中。
这样我们就得到了一个强名称程序集。如果有程序引用了该程序集,会记录由以下内容标识的程序集:
MyType, Version=1.0.3087.28686, Culture=neutral, PublicKeyToken=337642649f453c2c
这些内容唯一的标识了一个强名称程序集,由于公钥太长,这里只引用了公钥标记。当程序运行时,CLR 会根据这些内容去搜寻程序集,只有完全匹配的程序集才会被加载,即便是版本的细微差别都不会忽略。如果没有找到,或者找到的程序集不匹配,都会产生异常。
Strong Name(强名称)主要作用是用来程序集的统统一命名,通过文件名称、版本号(AssemblyVersion)、数字密钥的公钥记号(Public Key Token)、程序集的区域性设置(Culture)4部分信息来区分程序集。公钥记号还有一个重要用途,就是用来验证大型组织(也不一定是大型组织,只要你知道他的公钥记号就好)开发的.NET程序集。这样可以让程序集无法被伪造,安全性得到了提高。
首先,来谈一下版本号(文件名称就放过了:P),在程序集的Attribute中一共有三种版本号,分别是AssemblyFileVersion、AssemblyInformationalVersion、AssemblyVersion:
(1)、AssemblyFileVersion:为编译器生成的文件加入版本号;
(2)、AssemblyInformationalVersion:加入产品版本号;
(3)、AssemblyVersion:这才是用于定义强名称的版本号;这个版本号由四部分组成分别用"."隔开例如3.0.6701.9其中3为主版本号在最前面,后面一个0为副版本号,再后面6701为编译生成号,最后面的9为修订号。在设定Assembly的AssemblyVersion属性时,可是使用"*"来声明有编译器生成编译生成好和修订号,例如2.3.*,则编译生成号为 2000年1月1日起到编译日期止累计的天数,而修订号则是当天累计的秒数(实际为秒数/2,因为24×60×60 = 86400>65536),用这种日期机制来生成版本号,有助于生成持续增加且永不重复版本号;
现在来继续看一下数字密钥的公钥记号,他也对应着Assembly的一个属性,名字叫做AssemblyKeyFiles,他将接受一个由sn.exe(Visual Studio2005可以在"vs安装目录\SDK\v2.0\Bin"下找到)工具生成的.snk(strong name key强名称密钥)文件(例如:sn -k MyKey.snk)作为参数,.snk文件包含一个公钥/密钥对(可以用sn -tp Mykey.snk来查看.snk文件,但是不知道为什么有时候没法查看总是提示:未能将密钥转换为标记 --程序集" <null> "的公钥无效),在这里,使用公钥/密钥对,进行加密主要目的是通过签名防止程序集的伪造,签名的具体过程是:
(1)、在编译后签名前对程序集的所有非主模块计算出其散列值,并保存在主模块清单中的FileDef中;
(2)、通过一定的方式对存在于主模块清单FileDef中的散列值和主模块一起计算出其对应的散列值,并将散列算法的引用保存在主模块清单中的AssemblyDef中;
(3)、对上面算出的主模块的散列值通过密钥(私钥)进行加密,并将加密后的值作为数字签名保存在主模块的CLR文件头中,并将公钥保存在主模块清单的AssemblyDef中。
这样就通过这种公钥/密钥对可以互相加密解密的特性,可以保证如果通过公钥解密的数字签名和当前主模块计算出散列值相同则为真正的程序集,否则则为伪造。
这里如果在程序集的开发过程中,有大量的人介入,每个负责编译强名称程序集的人都需要访问包含公钥/密钥对文件,这样就很难保证密钥不会被泄露出去,为了尽量防止这种情况的发生,便诞生了一种叫做延迟签名(delayed signature)的机制,这种机制的运作方式是:
(1)、首先通过sn -p命令产生一个只包含公钥信息的.snk文件,例如:sn -p MyKey.snk MyPubKey.snk,
后面的这个MyPubKey.snk便是一个只包含公钥信息的.snk文件;
(2)、然后呢通过用true来初始化AssemblyDelaySignAttribute来激活delayed signature,这样开发人员直接使用MyPubKey.snk来代替MyKey.snk来进行开发,编译器不会对主模块进行签名,但是会保留出主模块中用来签名的空间,并且还会将MyPubKey.snk中的公钥信息插入到主模块清单中的AssemblyDef中,并且计算各非主模块的散列值插入到主模块清单的FileDef中。
(3)、最后当开发完毕准备打包部署时,只要使用sn.exe工具的-R(注意大写)选项就可以完成签名,例如:sn.exe -R Assname.exe MyKey.snk。
可以通过sn -T(注意大写)来查看程序集的公钥记号(Public Key Token)。如果程序集的公钥记号是b03f5f7f11d50a3a那么他就石油微软公司提供的,如果过是b77a5c561934e089那么就是有ECMA组织提供的,除非他们的密钥被破解,或者改变,否者这是永久成立的。
一旦程序集进行了数字签名,它就具有了请名称。
最后,我们来简单的了解一下程序集的区域性设置,区域设置(Culture)是为了程序能够让不同的国家、不同语言的用户来使用而产生的一种机制,他需要为程序支持的每一种区域设置提供一组应用程序呈现给用户的内容(字符串、图片、动画、声音、日期和数字格式等),着被称为应用程序的全球化(globalization)或国际化(internationalization)或本地化(localization)。
区域设置是国家和语言的组合体,比如“en-US”就是表示英语(美国)“fr-CA”表示法语(加拿大),区域设置通过CultureInfoAttribute来实现。
区域设置作为程序集强名成的一部分,但是任何包含代码的程序集必须采用区域无关的设置即提供给CultureInfoAttribute的字符串为空,如果过要用区域设置作为资源的索引就要创建一种仅包含资源的程序集--卫星程序集(satellite assembly)。
关于区域设置
关于强名称
强名称是标识一个配件的方法(当然也可用于标识一个.Net 应用程序, 应用程序配件单或部署配件单)。它具有充分限定(full qualified)的特性, 可以全局唯一标识一个配件。它由以下几个部分组成:
+
简单的用于描述配件的名称, 如mscorlib;
+
版本号;
+
文化信息, 包含关于配件的语言环境信息, 缺省为不确定(neutral);
+
公有密钥 (Public Key);
+
数字签名 (Digital Signature);
公有密钥和数字签名用于保证配件内容不会被第三方轻易篡改。关于它们的详细信息请参考第3节。
强名称的作用
和以往非托管(Native/Unmanaged)的组件,如COM,普通的DLL,及弱名称配件仅仅使用简单的名称标识自身相比,使用强名称的配件具有如下的好处:
1)可以精确地标识一个配件
使用强名称能够精确区分不同的配件,强名称中的版本信用于识别同一个配件不同的版本,从而避免版本冲突问题,比如DLL地狱(DLL Hell)。
2)防止配件内容被篡改
强名称对配件的内容采用了相应的签名机制,配合相应的验证机制,虽然不能完全避免但可防止配件内容被第三方轻易篡改。
# 其实与传统的直接被编译成机器码的程序不同,.Net 配件是以托管代码形式保存在PE格式的文件中的,也就是以微软中间语言代码(MSIL)形式存在。因此只要对MSIL比较熟悉,第三方就可以很容易地对.Net配件进行篡改,所以为配件使用强名称是保护配件内容安全的方法之一。
强签名的实现
强签名
配件强签名的过程如下图所示:
角色、对象:
+
.Net Developer: .Net配件开发者;
+
Strong name Tool(Sn.exe)/Assembly Linker(Al.exe): 配件强签名工具
+
Strong-naming Assembly:强名称配件
+
Cryptography:密码系统
+ Consumer of Strong-named Assembly:强名称配件使用者
过程:
1)(1-2)配件开发者使用相应的工具如Sn.exe产生用于非对称加解密的密钥对,其包含了一个公有密钥和一个私有密钥。Sn.exe将创建并把密钥对保存到指定的文件中;
2)(3)配件开发者以保存密钥对的文件作为参数向配件连接器Al.exe请求对配件进行强签名,下面是一个例子:
al /out:Anor.dll Anor.module /keyfile:1.snk
# 除了使用配件连接器之外,我们也可以通过以下方式请求编译器对配件进行强签名:
*
使用编译器选项 如、/keyfie (C#),/KEYFILE(C++);
* 在配件源代码中使用配件属性指定密钥对文件。如在C#源代码中可以这样指定:
[assembly:AssemblyKeyFile("1.snk")]
3)(4-7)强签名工具(Al.exe,或相应.Net语言编译器)获取配件内容,并请求密码系统对其进行散列,密码系统对配件内容进行散列后返回其哈希值;
4)(8-9)强签名工具从密钥对文件中摘取私有密钥(Private Key),向密码系统申请使用私有密钥对配件内容进行非对称加密,密码系统随后返回加密后的结果,也就是所谓的数字签名(Digital Signature);
5)(10-11)签名工具从密钥对文件中摘取出公有密钥(Public Key),连同将获得的配件的数字签名一起存储在配件的装配单中(Assembly Manifest);
6)(12-17)强名称配件使用者引用强名称配件后,在编译时,编译器将从强名称配件中获取该强名称配件的公有密钥(Public Key),然后请求密码系统对公有密钥进行散列,随后把密码系统返回的哈希值保存在强名称配件使用者的装配件中。
#对公有密钥散列后的哈希值称之为公有密钥令牌(Public Key Token)。强名称配件使用者之所以保存公有密钥令牌而不是公有密钥本身是为了节省自己的存储空间,因为令牌比密钥本身所需的存储空间少得多。
延迟签名(Delay Signing)
从3.1可以看出对配件进行签名同时需要公有密钥和私有密钥。在大型项目中,往往存在大量的分别有不同开发人员开发的需要强签名的配件,这些开发人员都需要使用私有密钥。许多人对私有密钥的使用可能会影响私有密钥的安全,私有密钥应该掌握在少数授权人手中。另外由于开发过程中配件需要反复的进行编译、测试、修改,每次都进行强签名是没有必要的。其实配件只要在最后发布前强签名就可以了。
为此可以使用.Net延迟签名机制。配件在开发过程中,开发人员只需使用公有密钥(可以使用Sn.exe从密钥对中摘取出来)对配件进行延迟签名,这时3.1的步骤2就有些变化了。强签名工具不会获取配件的数字签名,而仅把公有密钥保存在配件的装配单中,同时为将来产生的数字签名保留存储空间。
当配件开发完成,在发布前,再使用Sn.exe等强签名工具配合私有密钥对配件强签名。
这就是所谓的延迟签名。
强签名配件验证
经过第3节的强签名之后,配件中包含了公有密钥和数字签名,这些信息将成为强签名验证机制(如.Net CLR验证)识别配件和验证配件内容是否被修改的依据。利用这些信息,同时结合强名称配件使用者所拥有的公共密钥令牌,.Net CLR可以验证所引用的配件内容是否被篡改。其过程如下图所示:
角色、对象:
+
Consumer of Strong-named Assembly:强名称配件使用者
+
Common Language Runtime(CLR):.Net公共语言运行时
+
Strong-named Assembly:强名称配件
+ Cryptography:密码系统
过程:
1)(1)强名称配件使用者引用强名称配件,向CLR提供配件的强名称信息,其中包括它所持有的强名称配件的公有密钥令牌。CLR根据提供的信息加载相应强名称配件,加载之前需要对配件进行验证;
2)(2-6)验证公共令牌。CLR从强名称配件中获取公有密钥并使用密码系统对其进行散列,然后把散列后的值同强名称配件使用者提供的公有密钥令牌相比较,判断是否相同,如果不同说明至少有一方的公共密钥相关数据被修改了,验证失败;
3)(7-15)CLR获取强名称配件的内容,然后使用密码系统对配件内容进行散列,获得配件散列后的哈希值。同时CLR请求密码系统使用存放在强名称配件的装配件中的公共密钥对同样保存在其中的数字签名进行解密得到一个哈希值。最后比较上述两个哈希值,如果相同说明强名称配件完好无损,未被篡改,验证通过,否则验证失败;
#上述过程中,CLR使用的散列算法应该和强名称工具对强名称配件公共密钥散列时使用的算法一致。实际上,CLR可以从强名称配件使用者的装配件中获知后者使用的散列算法。
本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/zgh2002007/archive/2009/03/19/4005323.aspx