NET程序集强签名

时间:2023-02-20 21:08:42

强名称是由程序集的标识加上公钥和数字签名组成的。其中,程序集的标识包括简单文本名称、版本号和区域性信息(如果提供的话)。强名称是使用相应的私钥,通过程序集文件(包含程序集清单的文件,并因而也包含构成该程序集的所有文件的名称和散列)生成的。Microsoft® Visual Studio® .NET 和在 .NET Framework SDK 中提供的其他开发工具能够将强名称分配给一个程序集。强名称相同的程序集应该是相同的。

通过签发具有强名称的程序集,您可以确保名称的全局唯一性。强名称还特别满足以下要求:

  • 强名称依赖于唯一的密钥对来确保名称的唯一性。任何人都不会生成与您生成的相同的程序集名称,因为用一个私钥生成的程序集的名称与用其他私钥生成的程序集的名称不相同。

  • 强名称保护程序集的版本沿袭。强名称可以确保没有人能够生成您的程序集的后续版本。用户可以确信,他们所加载的程序集的版本出自创建该版本(应用程序是用该版本生成的)的同一个发行者。

  • 强名称提供可靠的完整性检查。通过 .NET Framework 安全检查后,即可确信程序集的内容在生成后未被更改过。但请注意,强名称中或强名称本身并不暗含信任级别,例如由数字签名和支持证书提供的信任。

在引用具有强名称的程序集时,您应该能够从中受益,例如版本控制和命名保护。如果此具有强名称的程序集以后引用了具有简单名称的程序集(后者没有这些好处),则您将失去使用具有强名称的程序集所带来的好处,并依旧会产生 DLL 冲突。因此,具有强名称的程序集只能引用其他具有强名称的程序集。

使用程序集和全局程序集缓存  

如果您需要在几个应用程序间共享程序集,可将其安装到全局程序集缓存中。安装了公共语言运行库的每台计算机均具有此计算机范围的代码缓存。全局程序集缓存中存储了专门指定给由计算机中若干应用程序共享的程序集。要安装到全局程序集缓存中,程序集必须具有强名称。

NET程序集强签名注意

全局程序集缓存中放置的程序集必须具有相同的程序集名称和文件名(不包括文件扩展名)。例如,程序集名称为 myAssembly 的程序集必须具有名为 myAssembly.exe 或 myAssembly.dll 的文件。

应当只在必要时才将程序集安装到全局程序集缓存中来共享程序集。一般原则是:程序集依赖项保持专用,并在应用程序目录中定位程序集,除非明确要求共享程序集。另外,您不必为了使 COM interop 或非托管代码可以访问程序集而将程序集安装到全局程序集缓存。

要将程序集安装到全局程序集缓存中的原因有以下几点:

  • 共享位置。

    可将应用程序应该使用的程序集放在全局程序集缓存中。例如,如果所有的应用程序都应使用位于全局程序集缓存中的程序集,则可将版本策略语句添加到 Machine.config 文件(此文件将引用重新定向到程序集)。

  • 文件安全性。

    管理员通常使用访问控制列表 (ACL) 来保护 systemroot 目录,以控制写入和执行访问。因为全局程序集缓存安装在 systemroot 目录中,它继承了该目录的 ACL。建议只允许具有“管理员”权限的用户从全局程序集缓存中删除文件。

  • 并行版本控制。

    可在全局程序集缓存中维护程序集的多个副本(名称相同但版本信息不同)。

  • 其他搜索位置。

    在探测或使用配置文件中的基本代码信息之前,公共语言运行库会先检查全局程序集缓存中符合程序集请求的程序集。

请注意,在有些情况下,您肯定不需要将程序集安装到全局程序集缓存中。如果将组成应用程序的某个程序集放在全局程序集缓存中,就无法再通过使用 XCOPY 复制应用程序目录来复制或安装应用程序。在这种情况下,还必须将程序集移到全局程序集缓存中。

全局程序集缓存

要使用 Visual Studio .NET 创建小型类库项目、生成强名称,以及在 GAC 中安装项目的 .dll 文件,请按照下列步骤操作:

1. 在 Visual Studio .NET 中,创建一个新 Visual C# .NET 类库项目,并将该项目命名为 GACDemo
2. 必须使用强名称。要生成此加密密钥对,请使用 SN 工具。此工具位于安装 .NET Framework 解决方案开发人员工具包 (SDK) 的 /bin 子目录中。SN 工具易于使用。命令行语句采用以下形式:
sn -k "[DriveLetter]:/[DirectoryToPlaceKey]/[KeyName].snk"
3. 在 C:/ 中创建一个名为 GACKey 的目录,以便您可以轻松地找到密钥,并从命令提示符处访问该密钥。

注意:对于大多数用户,.NET 工具位于 C:/Program Files/Microsoft.NET/FrameworkSDK/Bin 中。键入以下 SN 命令前,可能需要在您的计算机上将与该路径类似的路径复制到 .NET bin 目录中。从命令提示符处键入 cd,右键单击以粘贴该路径,然后按 Enter,快速转至 SN 工具所在的目录。

键入以下内容:
sn -k "C:/GACKey/GACkey.snk"
4. 将生成一个密钥,但是它与项目的程序集尚无关联。要建立此关联,请在 Visual Studio .NET 解决方案资源管理器中双击 AssemblyInfo.cs 文件。此文件具有一个程序集属性列表,默认情况下,在 Visual Studio .NET 中创建项目时将包括这些属性。在代码中修改“AssemblyKeyFile”程序集属性,如下所示:
[ assembly:AssemblyKeyFile("C://GACKey//GACKey.snk")]
通过按 Ctrl+Shift+B 来编译项目。您不必具有任何附加代码即可以在 GAC 中安装 .dll 文件。
5. 您可以通过使用 Gacutil 工具或者通过将 .dll 文件拖至适当的目录来安装 .dll 文件。如果您使用 Gacutil 工具,则可以使用以下命令:
gacutil -I "[DriveLetter]:/[PathToBinDirectoryInVSProject]/gac.dll"
如果您要拖动文件,请使用 Microsoft Windows 资源管理器。打开 Windows 资源管理器的两个实例。在一个实例中,找到控制台项目的 .dll 文件输出的位置。在另一实例中,找到 c:/[SystemRoot]/Assembly。

将您的 .dll 文件拖到“Assembly”文件夹中。

用强名称来给程序集签名即谓之程序集强签名!

那什么是强名称,签名又有什么作用?,我们先看看强名称的概念是什么,强名称是由程序集的标识加上公钥和数字签名组成的,其中程序集的标识包括简单文本名称,版本号和区域性信息!

Visual Studio.NET 和 .NET Framework SDK 中有工具能够将强名称分配给一个程序集!强名称相同的程序集一般也是相同的!

好了,具体我们可以通过给程序集强签名达到什么目的呢?

通过签发具有强签名的程序集合,可以确保名称的全局唯一性!因为强名称是依赖于唯一的密钥对来确保名称的唯一性,其他人不会生成与你相同的程序集名称(不同的私钥产生的名称不同)

强名称保护程序集的版本沿袭,因为强名称的唯一性能够确保没有其他人能够生成你的程序集的后续版本

强名称提供可靠的完整性检查,通过.NET Framework安全检查后,可以确保程序集内容在生成后未被更改过!

要注意的是,具有强名称的程序集引用其他程序集,如果这个程序集没有强名称,那么具有强名称的程序集所带来的好处,并依旧会产生DLL冲突!因此具有强名称的程序集只能引用其他具有强名称的程序集。 

 

 

强命名程序集(Strong Name Assembly)的概念

  因为不同的公司可能会开发出有相同名字的程序集来,如果这些程序集都被复制到同一 个相同的目录下,最后一个安装的程序集将会代替前面的程序集。这就是著名的Windows “DLL Hell”出现的原因。

  很明显,简单的用文件名来区分程序集是不够的,CLR需要支持某种机制来唯一的标识一个程序集。这就是所谓的强命名程序集。

  一个强命名程序集包含四个唯一标志程序集的特性:文件名(没有扩展名),版本号,语言文化信息(如果有的话),公有秘钥。

  这些信息存储在程序集的清单(manifest)中。清单包含了程序集的元数据,并嵌入在程序集的某个文件中。

  下面的字符串标识了四个不同的程序集文件:

  “MyType, Version=1.0.1.0,

  Culture=neutral, PublicKeyToken=bf5779af662fc055”

  “MyType, Version=1.0.1.0,

  Culture=en-us, PublicKeyToken=bf5779af662fc055”

  “MyType, Version=1.0.2.0,

  Culture=neturl, PublicKeyToken=bf5779af662fc055”

  “MyType, Version=1.0.2.0,

  Culture=neutral, PublicKeyToken=dbe4120289f9fd8a”

  如果一个公司想唯一的标识它的程序集,那么它必须首先获取一个公钥/私钥对,然后将共有秘钥和程序集相关联。不存在两个两个公司有同样的公钥/私钥对的情况,正是这种区分使得我们可以创建有着相同名称,版本和语言文化信息的程序集,而不引起任何冲突。

  与强命名程序集对应的就是所谓的弱命名程序集。(其实就是普通的没有被强命名的程序集)。两种程序集在结构上是相同的。都使用相同的PE文件格式,PE表头,CLR表头,元数据,以及清单(manifest)。二者之间真正的区别在于:强命名程序集有一个发布者的公钥/私钥对签名,其中的公钥/私钥对唯一的标识了程序集的发布者。利用公钥/私钥对,我们可以对程序集进行唯一性识别、实施安全策略和版本控制策略,这种唯一标识程序集的能力使得应用程序在试图绑定一个强命名程序集时,CLR能够实施某些“已确知安全”的策略(比如只信任某个公司的程序集)。

 

 

How:实现.NET Strong Named(强签名)防止程序被篡改   

对.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 重新签名,就可以发布了。 

强名称工具 (Sn.exe) 来创建密钥对:

要使用强名称为程序集签名,必须具有公钥/私钥对。这一对加密公钥和加密私钥用于在编译过程中创建强名称程序集。您可以使用强名称工具 (Sn.exe) 来创建密钥对。密钥对文件通常具有 .snk 扩展名。

创建密钥对

  • 在命令提示处,键入下列命令:

    sn –k <file name>

    在此命令中,“文件名”是包含密钥对的输出文件的名称。

下面的示例创建名为 sgKey.snk 的密钥对。



sn -k sgKey.snk

如果您需要延迟对程序集签名并控制整个密钥对(密钥对不太可能在测试方案之外),可使用以下命令生成密钥对,然后从中将公钥提取到一个单独的文件中。首先,创建密钥对:

 
sn -k keypair.snk
  • 下一步,从密钥对中提取公钥,并将其复制到一个单独的文件中:

 
sn -p keypair.snk public.snk
  • 创建密钥对之后,必须将文件放在强名称签名工具可以找到的位置。

当使用强名称对程序集进行签名时,程序集链接器 (Al.exe) 查找与当前目录和输出目录相关的密钥文件。当使用命令行编译器时,只需将密钥复制到包含代码模块的当前目录即可。

如果要使用 IDE(例如 Visual Studio 2005)为程序集签署强名称,则必须知道 IDE 查找密钥文件的位置。例如, C# 编译器则在包含二进制文件的目录下查找密钥文件。将密钥文件放在适当的项目目录中并设置文件属性,如下所示:



C#



[assembly: AssemblyKeyFileAttribute(@"../../key.snk")]