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时,创建的公匙/私匙都不同
→从密匙文件中提取公匙部分,将公匙另存为一个公匙文件
于是,在F:\m文件夹下多了Darren.pk公匙文件
→查看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();
}
.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }
结果:

→查看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();
}
.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }
结果:

→从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之美》--张子阳,感谢写了这么好的书!
29防止程序集被篡改仿冒,全局程序集缓存GAC的更多相关文章
- 如何将程序集安装到全局程序集缓存GAC
针对一些类库项目或用户控件项目(一般来说,这类项目最后编译生成的是一个或多个dll文件),在程序开发完成后,有时需要将开发的程序集(dll文件)安装部署到GAC(全局程序集缓存)中,以便其他的程序也可 ...
- C#程序集系列11,全局程序集缓存
全局程序集缓存(GAC:Global Assembly Cache)用来存放可能被多次使用的强名称程序集.当主程序需要加载程序集的时候,优先选择到全局程序集缓存中去找寻需要的程序集. 为什么需要全局程 ...
- 无法安装或运行此应用程序。该应用程序要求首先在"全局程序集缓存(GAC)"中安装程序集
在做winform程序发布时遇到了这个问题,在我的机子上是可以正常运行的,但到别人的机子上就出现了这个错误.为此问题头疼了一上午终于搞定! 遇到这个问题一定是配置环境的原因, 1.你可以在程序 发布 ...
- 全局程序集缓存GAC
GAC中的所有的Assembly都会存放在系统目录"%winroot%\assembly下面.放在系统目录下的好处之一是可以让系统管理员通过用户权限来控制Assembly的访问. 目录:C: ...
- 【C# 程序集】在.net中使用GAC 全局程序集缓存
原文地址:https://blog.alswl.com/2011/01/gac/ GAC GAC是什么?是用来干嘛的?GAC的全称叫做全局程序集缓存,通俗的理解就是存放各种.net平台下面需要使用的d ...
- Gacutil.exe(全局程序集缓存工具)
全局程序集缓存 .NET Framework (current version) 其他版本 安装有公共语言运行时的每台计算机都具有称为全局程序集缓存的计算机范围内的代码缓存.全局程序集缓存中存储了专门 ...
- 【转】Gacutil.exe(全局程序集缓存工具)
全局程序集缓存工具使您可以查看和操作全局程序集缓存和下载缓存的内容. 安装 Visual Studio 和 Windows SDK 时会自动安装此工具. 要运行工具,我们建议您使用 Visual St ...
- C#中的全局程序集缓存定义
安装有公共语言运行时的每台计算机都具有称为全局程序集缓存的计算机范围内的代码缓存.全局程序集缓存中存储了专门指定给由计算机中若干应用程序共享的程序集. 应当仅在需要时才将程序集安装到全局程序集缓存中以 ...
- 全局程序集缓存工具(Gacutil.exe)用法详解
全局程序集缓存工具 (Gacutil.exe) 全局程序集缓存工具使您可以查看和操作全局程序集缓存和下载缓存的内容. 复制 gacutil [options] [assemblyName | asse ...
随机推荐
- java 内部类可以被覆盖吗
如果创建了一个内部类,然后继承其外围类并重新定义内部类时,"覆盖"内部类就好像是其外围类的一个方法,并不起作用, 这两个内部类是完全独立的两个实体,各自在自己的命名空间内 //: ...
- 有关c语言编程
有关C语言编程 统计代码"行数" 对于统计代码"行数",行数不包括空白行和注释行.程序改进如下: while(fgets (mystring , 100 , f ...
- CCF201712真题
试题编号: 201712-1 试题名称: 最小差值 时间限制: 1.0s 内存限制: 256.0MB 问题描述: 问题描述 给定n个数,请找出其中相差(差的绝对值)最小的两个数,输出它们的差值的绝对值 ...
- CCF CSP 201412-2 Z字形扫描
CCF计算机职业资格认证考试题解系列文章为meelo原创,请务必以链接形式注明本文地址 CCF CSP 201412-2 Z字形扫描 问题描述 在图像编码的算法中,需要将一个给定的方形矩阵进行Z字形扫 ...
- Kafka(四)Kafka在zookeeper中的存储
一 Kafka在zookeeper中存储结构图 二 分析 2.1 topic注册信息 /brokers/topics/[topic] : 存储某个topic的partitions所有分配信息 [zk: ...
- PHP递归遍历数组 不破坏数据结构 替换字符
代码如下: <?php $arr = array('0'=>array("<小刚>","<小晓>","<小飞 ...
- html中<b>标签和<Strong>标签的区别
关于html标签中b和strong两个的区别,我也是今早上才注意的,以前都是混着用的,早上看书的时候才注意到这两个标签的区别. 用在网页上,默认情况下它们起的均是加粗字体的作用,二者所不同的是,< ...
- thinkphp getField()获取一列或一个数据
在开发中经常要获取一个数据的情况,thinkphp中有一个getField()方法可以解决这个问题. 获取一个数据 1 2 $user = M('demo'); $data = $user->g ...
- thinkphp中常用的模板变量
在thinkphp中的模板要加载静态文件如css,js等文件时要经常用到模板常量. 假如项目放在/web/shop中,则如下所示对应常量的输出值: 1 2 3 4 5 6 7 8 9 // 不含域名 ...
- ES6 一些笔记
一. let/const: 1. “暂时性死区”概念:在代码块内,使用let/const命令声明变量之前,该变量都是不可用的.这在语法上,称为“暂时性死区”(temporal dead zone,简称 ...