先说点题外话。首先要寒一下自己,有半年多没有更新blog,实在是偷懒至极。。其间心得倒也不少,如果只是贴代码,倒是挺容易,不过这样的post在我看来,是最垃圾的,肯定骂声无数;但要把问题讲清楚透彻,又比写代码要费劲得多
言归正传。在我看来,利用.NET开发应用程序,有两个比较突出的问题,就是性能和安全性。当然在Web应用上这两点倒不是很大的问题。
安全方面,除了强名称和混淆编译等部署端的保护外(其实没什么用),.NET还提供了代码访问权限,我们今天只讨论其中的强名称权限。
首先,我们知道,经过强名称签名的程序集,可以拥有严格的版本控制和命名保护。而通常情况下,同一个系统如果拥有多个程序集(DLL或EXE),一般都会使用相同的密钥去进行强名称签名。而第三方只能看到强名称的公钥部分。也就是说,除非拥有这个密钥,否则得不到相同的签名公钥。
强名称代码访问保护(StrongNameIdentityPermission),就是被保护的代码,只有相同签名的程序集才有权访问。这样,即使别人得到你的DLL,也无法使用它。
一、使用强名称代码保护
我们来看一个简单的例子。
1、创建强名称签名密钥对:
在命令行运行:sn -k KeyPair.snk,即可以将新产生的密钥对存放在KeyPair.snk文件中;
2、新建一个DLL项目(SNTest.DLL),在AssemblyInfo.cs中使用该强名称:
[assembly: AssemblyKeyFile("KeyPair.snk")]
3、创建测试方法:
public static string Hello() { return "Hello"; }
4、注意:这是关键。为这个方法添加强名称保护。
[System.Security.Permissions.StrongNameIdentityPermission(
System.Security.Permissions.SecurityAction.LinkDemand, PublicKey=
"0024000004800000940000000602000000240000"+
"5253413100040000010001005be5ddabc8109e4c"+
"0ffa55d067153c9871fc1991ca21401a24f2d8e4"+
"391a77671fc50fd9791e3d01506f33bac51882a5"+
"89a7ca17ac687fe2b00550776026f264bf838b7c"+
"aefe207597db9d4d52677d7e5342a4ec6760ca7a"+
"eff47a3578f1a924589b930c8292e324c5a451e5"+
"0134c41a01c234fb384a4a7c35656921eee45fb4") ]
public static string Hello() { return "Hello"; }
我们来分析一下。由于默认的代码访问权限是CLR控制的,所以直接通过在类或者方法前添加 StrongNameIdentityPermission 特性,就可以对代码进行保护。参数里的 PublicKey,就是允许访问该代码的程序集的强名称签名公钥部分。而SecurityAction则表示安全操作的类型。常用的有SecurityAction.Demand(要求堆栈里所有调用都拥有此权限)和SecurityAction.LinkDemand(要求堆栈里该方法的直接调用者拥有此权限)。
这里顺便提一下程序集强名称签名公钥的获得方法:在命令行输入 [sn -Tp 程序集名称]
sn -Tp SNTest.DLL
继续我们的例子。
5、再新建一个控制台EXE项目(SNTest.EXE),使用同一个 KeyPair.snk 进行签名
6、在主函数调用受保护的那个测试方法:
public static void Main() { Console.WriteLine( SNTest.Class1.Hello() ); }
运行看一下,现在是相同强名称程序集去访问,应该可以看到输出 "Hello"。
如果我们把 SNTest.EXE 的强名称去掉,或者对它使用另外一个密钥对进行签名,那么因为权限不够,将会抛出 SecurityException。
二、反射调用
大部分的情况,我们都可以用上面的方法进行代码的强名称访问权限保护。但是,你有没有和我一样,碰到过这样的应用:我们不是直接调用受保护方法,而是通过反射。这个时候仍会抛出 SecurityException!
原因应该比较好分析。强名称代码保护中,无论 SecurityAction.Demand 还是 SecurityAction.LinkDemand ,所要求的访问权限(强名称签名)都是很严格的。即,直接调用者,必须是指定签名的程序集。而使用反射,看起来好像也是我们的代码直接调用保护代码,但实际上反射是系统实现和调用的。流程如下:
我们的代码 -> 反射的类和方法(System.Refecltion下) -> 受保护代码
跟踪调用堆栈,得到的也是相同的结论。使用反射,必然会通过系统调用。
三、自己实现强名称代码保护
问题的解决,应该是有很多的思路和方法。比如继承 StrongNameIdentityPermission 类;或者继承 CodeAccessPermission,实现自己的代码保护权限。而我目前的解决方案,是自己写一个类(StrongNameCodeAccessPermission),检查它的调用堆栈里每个程序集的签名。之所以不用前个方案,一是因为.NET的代码访问实现起来比较复杂,二是自定义代码保护,还需要在机器上进行特殊的配置,反而不是很通用。
StrongNameCodeAccessPermission 是一个类,而不是Attribute,使用的时候也很简单,在需要保护的类的构造函数,或者特定方法的开头调用一下 StrongNameCodeAccessPermission .LinkDemand 即可:
public void AnotherProtected() {
StrongNameCodeAccessPermission .LinkDemand();
DoSomeThing();
}
以下是 StrongNameCodeAccessPermission 的代码,注释写得比较清楚,就不罗索了 ^^ 大家可以根据自己的需要修改这段代码。
一,使用 System.Diagnostics 里面的类获得调用堆栈;
二,为了解决前面谈到的反射问题,在检查时忽略了系统调用,也就是 System.DLL, System.Web.DLL, System.Windows.Forms.DLL 这类微软的程序集。它们的强名称公钥是:b77a5c561934e089 和 b03f5f7f11d50a3a,如果有遗漏的话大家补充上即是。
三,我这里获得强名称签名公钥,是通过 Assembly.GetCallingAssembly().GetName().GetPublicKeyToken() , 其实也可以从证据里面获得。网上有此类资料。
四,目前检查的是调用方程序集必须和被保护代码的程序集拥有相同的强名称签名。这应该是很常见的场合。。
BTW:千万不要相信这能保护你的代码和程序...























































































































