作为一个.Net的初学者,我一度想了解和理解strong name, 但是在网上搜了很多中文的资料后,发现大部分的资料对其的阐述不够全面和深入。这里我想谈谈自己的理解,希望能让人快速理解这个问题。本文中,我将从Why,What,how 阐述我的理解。
对.Net Assembly来说, 通过反射(Reflection)机制,得到一个Assembly里面所有的函数签名是很简单的.因此大家都可以制作一个和原Assembly具有完全一样接口的Assembly来让系统或者应用程序调用,这为dll hell提供了极大的便利。显然,.Net需要提供一种机制来防止这样的事情,尤其当我们从网上更新Assembly的时候,我们如何相信当前得到Assembly就是合法的那个呢? 一种办法是让Assembly的最初发布者提供签名,程序只相信具有同样签名的Assembly,而且除了作者,别人不能生成这个签名。.Net Runtime提供的就是这样一个机制。
首先我们看一下.Net Runtime是怎么识别和导入一个DLL的。在.Net中当我们说Assembly Name的时候,不是仅仅指其文件名(比如abc.dll)。实际 上,Assembly name是由四个部分构成的:Friendly Name,Culture, Pubilc Key(Token), Version。除了Friendly Name 和 Version 是一定有的,其他两个取决于开发者有没有指定它们。比如像我这样的初学者就不会去做一个有Public Key的程序,如果没有指定的话,Culture=neutral,PublicKeyToken=null。本文忽略Culture和Version对识别和导入一个DLL的影响,因为那是另一篇文章。我们知道,Assembly 导入有显示导入和隐式导入两种。显示指的是使用Assembly.Load或者Assembly.LoadFrom来动态导入一个Assembly到内存当中。隐式指的是CLR自动导入所要用到的Assembly(和我原先理解的不同的是,CLR并不是在在程序一开始运行就导入所有Reference的Assembly,而是当一个数据类型被用到的时候,再去解析和导入对应的Assembly)当CLR Loader决定导入一个Assembly的时候,它一定会导入和Reference阶段具有相同PublicKey或者PublicKeyToken的Assembly。否则导入失败。比如我们在Reference的abc Assembly的PublicKeyToken是1234123412341234,那么在运行阶段,它就会去找具有1234123412341234PublicToken的Assembly,找不到便抛出异常。当然如果Reference的Assembly不具备Publickey,.Net Runtime 就会省略这个过程,由此,我们可以看出Pulic Key或者PublicKeyToken就是我们需要的数字签名。我们将具有Public Key的程序称之为强签名程序。
那么到底什么是PublicKey和PublicKeyToken?Public Key没啥神秘,我们可以把它当成是128byte的数据加上32byte的头信息。它标识了一个Assembly的开发者。如果每次去Reference或者Load一个Assembly都需要输入这160字节的字符,未免太繁琐,于是我们可以用SHA 哈希算法(关于SHA标准,大家可以自己google一下)生成(我们不需要自己做,.Net SDK提供了这样的工具,下文会讲到)一个只有8个字节的PublicKeyToken,每次用这个PublickKeyToken就可以让.Net Compiler或者Loader找到相应的Assembly了。比如很多.Net自带的系统库,它的PublicKeyToken一般就是b77a5c561934e089,如果你用VisualStudio开发,你不需要指明这个,因为编译器对待系统库,会默认用这个PublicKeyToken。
拥有PubickKey并不能保证你的Assembly不被别人盗版使用,但是可以保证你的Assembly不被别人冒充。也就是别人不能生成和你有同样Publickey的Assembly。原来在生成强签名的Assembly的时候,PublicKey和Private Key要一起使用,是不是似曾相识呢,原来就是网络传输中的RSA公钥算法(关于RSA公钥算法,大家可以自己google一下)。Private Key可以当成是436字节的数据(字符串),Assembly的PublicKey是Assembly的使用者都可以得到的,但是PrivateKey就只有作者知道了。如果你是在一个很大的团队里开发一个很大的项目,恐怕你自己都不知道你开发的Asembly的Private Key(一般只有发布官才知道)。那么如果连程序员自己都不知道,他怎么调试他的Assembly呢?下文会介绍。
说了这么多理论,现在来看看具体怎么开发和部署具备Strong Name的Assembly吧,.Net SDK已经为我们提供了一个非常好的工具Sn.exe. 下面就Step by Step吧,
1. 产生一个试验性的Assembly,随便写点代码,用Visual Studio Class Library模板生成一个Assembly就叫 test.dll 吧. 代码如下:
namespace Test
{
public class Class1
{
public string A()
{
return "This is test App";
}
}
}
2.写一个Console程序,Reference test.dll,调用A(),如下,
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
Test.Class1 c = new Test.Class1();
Console.WriteLine(c.A());
}
}
}
程序输出:This is test App;
3.现在因为Test.dll不是Strong name的dll,任何人都可以冒用你的名义也生成一个test.dll,并且在#2的程序路径下替换原来那个,代码如下:
namespace Test
{
public class Class1
{
public string A()
{
return "This is a hack app";
}
}
}
现在运行以下#2中的程序,运行输出This is a hack app。
4. 用Sn.exe 生成一个Public/Private Key Pair 文件:Sn -k test.snk. 如果不指定大小,它的大小就是596 bytes(128 publicKey,32 publicKey Header, 436 PrivateKey)
5. 添加 [assembly: AssemblyKeyFile(@"test.snk")] 到程序的AssemblyInfo.cs中,也可以在Build Option中指定(/keyfile:test.snk ). 再重新生成test.dll. 在VisualStudio中还可以用工程属性指定
6. Sn -v test.dll 查一下test.dll是不是已经是一个strongname的程序了,输出:test.dll is valid。表示成功生成了一个具有PublicKey的程序 Sn -T test.dll 可以得到这个assembly的PublickKeyToken。
7. 用#2的程序重新引用新生成的test.dll, 重新编译#2的console程序。
8. 重复#3,现在程序运行失败了。因为它找不到具有相应PublicKey的test.dll.
9. 如果我是一个大项目的发布者,我不想让团队的所有开发者都知道privatekey file,那么应该使用 sn -p test.snk publickonly.snk, 生成一个只有Publick Key的文件,并让团队开发者暂时使用Publickonly.snk来给程序签名,并把test.snk藏起来,打死也不说。
10. 开发者拿到这有PublicKey的snk文件以后,他除了要做#5之外,还需要加上[assembly: AssemblyDelaySign(true)]. (也可以通过BuildOption指定),不然不能成功build。这样生成出来的Assembly便预留来空白位置,让日后发布时可以用 Sn -R test.dll test.snk重新签名。
11. 这个时候让#2重新引用新的test.dll 生成程序,运行#2还是会失败。因为Test.dll毕竟是一个没有用Private Key 签名的假的strong name Assembly.
12. 开发者想要启动#2 Debug test.dll, 那么还需要做 Sn -Vr test.dll 告诉Runtime我知道并认可这是一个临时签名的文件,让它运行吧。
13. 在发布前,发布官运行 sn -R test.dll test.snk 重新签名,就可以发布了。