29防止程序集被篡改仿冒,全局程序集缓存GAC
为什么需要强名称程序集和数字签名
有一个类库项目ClassLib,对应的程序集是ClassLib.dll。当前控制台项目引用ClassLib.dll程序集的方式有2种:
1、通过添加现有项目
文件→添加→现有项目→选择"ClassLib.csproj",把项目引入到当前控制台所在解决方案→右键控制台项目"引用"→添加引用→解决方案→项目→选择ClassLib项目
2、通过把程序集复制到当前项目文件夹下
在控制台项目下创建Library文件夹→把程序集ClassLib.dll拷贝到Library文件夹下→在控制台项目下引用该程序集
程序集的属性:
● 复制本地:True,表示在编译时会自动复制一份ClassLib.dll到当前项目bin/Debug中。
● 路径,表示ClassLib.dll程序集的所在位置。
2种引用程序集方式比较:
● 通过项目引用,由于路径总是指向ClassLib项目下bin/Debug,总能获得最新的ClassLib.dll程序集
● 通过程序集引用,获得的ClassLib.dll程序集可能不是最新版本
为什么需要强名称程序集?
● 如果不的项目想引用ClassLib.dll程序集的不同版本,如何区分?
● 如果其它公司的的程序集也叫ClassLib.dll,并且被引入,如何区分?
● 程序集的公司名、版本号、GUID等显式地声明在Properties/AssemblyInfo.cs中,如何防止篡改?
可以通过为程序集赋予强名称和为程序集加数字签名来解决上面的问题。
为程序集赋强名称
→在F盘m文件夹下创建公匙/私匙(Public Key/Private Key)
在"开发人员命令提示"中输入:
于是,在F:\m文件夹下多了Darren.snk
注意:
● 公匙/私匙采用非对称RSA加密方式,并结合使用了散列函数(SHA1)
● 每次调用sn时,创建的公匙/私匙都不同
→从密匙文件中提取公匙部分,将公匙另存为一个公匙文件
→查看Darren.pk公匙文件
→在VS中,右键项目--属性--签名--勾选"为程序集签名"--选择刚创建的密匙文件,为程序集ClassLib.dll创建密匙
另外,还可以通过C#编译器为某个主程序创建密匙:
csc /keyfile:Darren.snk Program.cs
→查看ClassLib.dll的PublicKeyToken
注意:
sn -T 程序集,这里的T一定要大写,否则报"未能将密匙转换为标记 无效的程序集公匙"错。
全局程序集缓存
当多个程序引用同一个程序集时,可以将程序集放到一个共享文件夹,这个文件夹不是以文件名来对程序集进行区分,这个特殊的文件夹叫做"全局程序集缓存(Global Assembly Cache,GAC)",它的位置在C:\Windows\aasembly,在运行阶段会使用这里的程序集。而在开发和编译阶段使用的是:C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\版本号\下的程序集。
查看System.Data的属性:
□ 自定义开发、编译阶段的程序集缓存及文件夹
→创建F:\SharedAssembly\ClassLib\v1.1
→把ClassLib.dll拷贝到F:\SharedAssembly\ClassLib\v1.1文件夹中
→将程序集ClassLib.dll安装到GAC中
→控制台项目引用F:\SharedAssembly\ClassLib\v1.1\ClassLib.dll
→生成项目,发现在控制台项目的bin\Debug下没有ClassLib.dll
可见,ClassLib.dll在全局程序集缓存中,不会在应用程序下拷贝一份ClassLib.dll。
→运行如下程序
static void Main(string[] args)
{
Class1 c = new Class1();
Console.WriteLine(c.GetContent());
Console.ReadKey();
}
→查看ClassLib.dll的属性
说明在开发和编译阶段使用的是F:\SharedAssembly\ClassLib\v1.1\ClassLib.dll。
→删除F:\SharedAssembly\ClassLib\v1.1下的ClassLib.dll,运行程序,还是得到与删除之前相同的结果
说明已经在使用全局程序集缓存了。
→再使用反射查看全局程序集缓存中的ClassLib.dll中的全名
static void Main(string[] args)
{
Assembly asm = Assembly.GetAssembly(typeof(ClassLib.Class1));
Console.WriteLine(asm.FullName);
Console.ReadKey();
}
→从GAC中卸载程序集
gacutil -u ClassLib,Version=1.1.0.0,Culture=neutral,PublicKeyToken=....
延迟签名
为什么需要延迟签名?
在团队开发中,如果将密匙文件提供给每位开发者,将会增加泄漏密匙文件的可能;如果不提供给团队成员,意味着在开发、编译、测试阶段只能使用非强名称程序集,在项目打包部署之前可能存在这样的做法:
1、删除掉之前所有引用的非强名称程序集,重新引用一遍签名过的强名称程序集,然后再全部重新编译一遍。
2、使用签名过的强名称程序集去覆盖同名的非强名称程序集,如果运行程序,会抛出异常"未能加载文件或程序集,找到的程序集清单定义与程序集引用不匹配"。
延迟签名可以很好地解决上面的问题:
→使用公匙进行标记,但是没有用私匙进行签名。
→缺少私匙签名的强名称程序集相当于一个被篡改过的强名称程序集,在正常情况下,CLR会拒绝加载它,并抛出"未能加载文件或程序集,强名称验证失败",为此,开发者需要指示CLR忽略对延迟签名程序集的验证,运行延迟签名程序集运行。
→在程序最终部署前,持有私匙的管理人员使用密匙文件对延迟签名程序集重新签名,也并不需要重新再编译引用了延迟签名程序集。
□ 延迟签名实例
→先把Darren.snk和以上ClassLib.dll程序集中的Class1.cs文件放到F:\asm中
→从公匙/私匙文件中提取公匙,单独保存在公匙文件中
在F:\asm文件中就多了Darren.pk公匙文件
→使用C#编译器csc.exe对程序集进行编译,并使用/delaysign+指定Darren.pk公匙文件
在F:\asm文件中就多了ClassLib.dll延迟签名程序集文件
→指示CLR或略对程序集的验证,该指令只对本台电脑有效。
如果想让CLR忽略对多个程序集的验证,可以通过通配符,并指定公匙文件:
D:\asm\sn -Vr * Darren.pk
总结
● 通过使用公匙/私匙文件为程序集签名,创建强名称程序集,可以并防止程序集被篡改和仿冒。
● 在团队开发中,可以考虑使用延迟签名,减少公匙/私匙文件泄漏的风险,并防止程序集被篡改和仿冒。
参考资料:
※ 《.NET之美》--张子阳,感谢写了这么好的书!