保护你的代码——谁动了我的组件?

Goofyyang(原作) 关键字 .NET security CLR

摘要:本文描述了如何用Code Access Security技术来保护代码,使代码不致被恶意调用。

作为一名.NET开发人员,你没日没夜地写代码,你的组件运行在越来越多的机器上。忽然有一天,你发现你写的组件被引用在别人写的项目里,而且最可气的是,那人竟用你的名义在做破坏它人系统的恶事!你忍不住了,大叫一声Oh shit!,然后打开MSDN,看看有什么办法能帮助你阻止这场阴谋。

OK,办法找到了,那就是.NET平台提供的Code Access Security。有大量继承于CodeAccessPermission的类可以帮你实现不同方面、不同范围的代码安全控制。你所需要做的只是从中挑出最适合的类别加以应用,从而达到保护你的组件的目的。

在经过一番挑选之后,你最终确定了使用StrongNameIdentityPermissionAttribute类。这个类允许你将组件(或类、方法)与某一强名称(通常就是你发布程序时所用的强名称)绑定,这样,只有在客户端程序具有该强名称签名的情况下才能使用你的组件。也就是说,除了你自己编写的客户端代码因为拥有同样的签名而被允许使用组件以外,任何第三方代码都无法通过StrongNameIdentityPermissionAttribute的防护,因此也就无法恶意调用你的组件了:)。听起来真的不错,马上就动手做吧!

为了简便起见,先创建一个很简单的Class Library项目,代码如下:

// SecureComp.dll
using System;

namespace musicland
{
    public class SecureComp
    {
        public string Confidential()
        {
            return "This is confidential!";
        }
    }
}

现在的这个组件非常可怜,因为任何人都可以写代码来调用它。下面,你就要耍点手段了:):

首先引入System.Security.Permissions命名空间:

using System.Security.Permissions;

然后,在组件级加上StrongNameIdentityPermissionAttribute属性:

[assembly: StrongNameIdentityPermissionAttribute(SecurityAction.RequestMinimum,
    PublicKey="0024000004800000940000000602000000240000525341310004000001000100c11c8497d"+
"283259f23d645358d65812b69136846b03a7d15124545fc3ed27d89d1330cceda4232c7bc6e8a0e7ecd857f8"+
"126d0859e2300237b3cab6f7737a92f585cbf2afb4b475c537703efb96e17e5921ff00c6e022b22f3d772f14"+
"6a3a5c7f6ccad3131b8d0465e6709e5a28cc3ca1c8b610af4162c1a18c0feb8e6993ab1")]
namespace musicland
…

注意,这里使用了SecurityAction.RequestMinimum,这申明除非获得StrongNameIdentityPermissionAttribute所表明的资源访问权限(即对SecureComp.dll组件的访问权限,可以把SecureComp.dll看作一样资源),否则CLR不会准许调用方(即客户端代码)访问所请求的资源;此外,在PublicKey属性中加入了你所允许的公匙(Public Key)的十六进制表示(转化成字符串类型)。CRL在运行期间将依照这一段公匙来判断调用方是否合法,除非调用方拥有相应的私匙(Private Key),否则将无法访问。看来,平时一定要倍加保护你的密钥文件,因为密钥文件(特别是private key)的泄露将会成为你无尽恶梦的根源,而延迟签名(delay signing)在这里也就显得格外重要了:.)

说到这里,你一定会有个大大的问号:这长长的一串PublicKey是怎么得出来的?难道要我凭空凑出来不成?当然不是。还记得那个Sn.exe工具吗?通过它就可以把PublicKey给提取出来。OK,打开你的命令行,定位到密钥文件所在目录并输入以下内容:

sn –p Key.snk PublicKey.snk

这样,提取出来的公匙信息就被存储在PublicKey.snk文件中。你现在只需把公匙信息读取出来并转化成适当的格式就可以了。这里,你可以使用.NET Framework自带的Secutil.exe工具,但据我所了解,Secutil工具的输出都是数组格式(我在自己的机器上测试了Secutil所提供的全部输出选项,但所得结果都是一样,这让我很感意外,不知大家是否有更好的办法),因此就动手自己写了一个小工具来完成这一读取和转换。大家如果感兴趣可以发邮件给我(因为我没有自己的网络空间可以存放。当然你也可以自己来写,因为它实在是太简单了,就是读取二进制文件)。它的运行界面如图1:

ReadKey

好了,现在你的代码就被全副武装起来了。试着写一个Console客户端来调用SecureComp,结果怎么样?是不是“无法获得相应权限”?试着用Key.snk给客户端程序签名后再访问,这回可以访问了吧!:)

结论:适当地应用Code Access Security可以使你的代码被保护起来,不致被第三方不正确调用;但是过多的安全保护也将造成代码运行效率下降,从而带来负面影响。

由于我也是处在学习过程之中,所以如果文中有错误、解释不彻底或可有更好的解决办法之处,还请大家指教。我的邮件地址是:yanghada@vip.sina.com。



对该文的评论 人气:5297

robertnet(2003-10-27 20:13:42)

智慧属于全人类,Copyleft.

yr_127(2003-10-27 15:12:28)

反编译后,别人可以看见原代码,你的方法不是很有用

Lorenes(2003-10-27 9:28:24)

想必大家都听说过 ILDASM.EXE 这个工具吧,即便用StrongName签名过的文件,一样可以反编译为IL格式的文件.
复制其签名属性,添加特殊代码的调用.
还是难以防范...
解决代码问题的根本:别让客户触及到程序体!
建议使用C/S模式编写程序.

Lorenes(2003-10-27 9:26:30)

想必大家都听说过 ILDASM.EXE 这个工具吧,即便用StrongName签名过的文件,一样可以反编译为IL格式的文件.
复制其签名属性,添加特殊代码的调用.
还是难以防范...
解决代码问题的根本:别让客户触及到程序体!
建议使用C/S模式编写程序.

Goofyyang(2003-10-25 17:20:59)

to AhBian:

你说的很有道理。其实我想大家应该都注意到了,我在文章里对于应用Code Access Security后对应用程序运行效率的影响只提了一下,而没有展开,本来我这样做的目的是想避免把文章篇幅担得太长,让人家抓不住主次。可现在看起来,大家直接就想到了效率和性能,这是我的失误。

的确,.NET Framework提供了多种Security的实现方法,有基于角色的(role-based),有基于代码的(code access security),有声明的(declaratively),有强制的(imperative),安全性检查的执行位置也有所不同,有程序集(assembly)级的,有类级的,也有方法级的…… 这些不同的选择和它们的组合会产生不同的保护效果,当然也会对程序的运行效率产生直接的影响,这就看大家怎么考虑、怎么取舍了。如果各位有兴趣的话可以去读MS Press的MCAD/MCSD教材(70-306/70-316),或者直接去查MSDN。我的这篇文章只是简单地牵个头,还有更多的东西需要考虑进去。在这个领域里,我和大家一样都需要不断努力学习。

freexin(2003-10-25 13:49:42)

使用这种方式开发Web项目进行延迟签名,就老是报安全错误,除非全部使用实时签名
但这样私钥又不能保密,估计.net在这方面还要改进一下

AhBian(2003-10-25 9:12:39)

我认为文中所提方法有点道理,但实现上有点菜。

千万不要放在 [assembly: ......] 中,这样会导致严重的性能问题,因为每次调用本程序集中的任何东西都会进行安全验证。

最好的方法,是放在一些重要或关键的构造函数或方法、属性上,如以下所示。

public class MyClass
{
#if RELEASE
    [System.Security.Permissions.StrongNameIdentityPermissionAttribute(
        System.Security.Permissions.SecurityAction.LinkDemand, 
          PublicKey="00240000048000009400000006020000002400005253413100040000010001000d9" +
        "ff4d0976f32515f8d8cb4214ad4bd32c7053bde79196a09f82bce5c920678ac58f0e1cbae183a" +
        "64b4eec4dc94abdd424df710ab4f6e1b687cc3e414bf35512bb2adb4826300fd910116c742a49" +
        "01afa0ffb845c64c0cbf75e011886637c18a74d997eb0cca7a664bf702a11f2b3e96557f80cae" +
        "124f8168297d018c146cd4")]
#endif
  public MyClass()
  {
      ....
  }

  ......
}

NOTE:
1、使用预编译指令 #if RELEASE ... #endif 可以避免在 DEBUG 条件下进行验证。
2、只能使用 System.Security.Permissions.SecurityAction.LinkDemand,而不是System.Security.Permissions.SecurityAction.Demand,否则任何情况都无法运行,因为.NET 基本类库都不具备匹配的签名私钥。
3、一定要在不是频繁执行访问的方法、属性或构造函数上设定,否则影响运行时性能。
在关键的、重要的类型上适当的设定即可起到作用。

作为正文的补充吧。

wubaozhang(2003-10-24 14:57:21)

mark

hxgui(2003-10-24 9:45:58)

garbage!! open source!

naxin(2003-10-24 4:19:51)

那java组件,咋办?

optman(2003-10-23 22:41:07)

关于签名,那本“Microsoft .NET Framework Programming”写很好。
.NET是比较厉害

Erickson(2003-10-23 16:09:38)

有点看不懂,
请问:什么是密钥文件,什么叫公钥和私钥。不知道怎么用Sn.exe和Secutil.exe工具
请板主把对用Secutil.exe工具输出的数据格式进行读取和转换的程序发给我吧,我的E-Mail:xjwei352@163.com
在此,先谢谢Goofyyang啦

Goofyyang(2003-10-23 10:46:52)

to PastHero:
我想你说的是给客户端签名,可以用[assembly: AssemblyKeyFile(@"d:\keys\myKey.snk")]来实现。既可以用在AssemblyInfo.cs文件中,也可以用在你的程序代码文件中,注意这是个assembly级的属性。

to aaxu:
你可以在客户端程序项目中添加一个对SecureComp.dll的引用,然后用它的全名musicland.SecureComp来引用(或者先引用命名空间以减少后续代码输入量)。因为SecureComp在assembly级应用了安全检查,所以当你调用musicland.SecureComp sc=new musicland.SecureComp()时就会引发CLR的安全性检查,CLR即会根据你的客户端程序是否有相应的签名来决定调用是否被允许。

SureBeiJing(2003-10-23 10:05:56)

好,果然是安全专家

aaxu(2003-10-23 8:40:49)

Console客户端来调用SecureComp怎么写

PastHero(2003-10-22 19:29:27)

如何签名,能否再介绍一下?

greystar(2003-10-22 16:18:04)

study

ripper(2003-10-22 12:22:10)

.net的特性决定了对于cracker来说没有代码安全可言。

cpilq(2003-10-21 19:12:11)

不错~~~

superhasty(2003-10-21 16:45:40)

good

Contributors: FHL