.net程序集强名称签名实践

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

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

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

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

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

虽然强名称是 .NET 加密领域的元老,也是微软推荐的应用程序保护机制,但是由于托管程序可以被反汇编成 IL 代码,更改或者去除强名称也就成了可能。强名称的保护强度也因此大大减弱。

9.2.1  使用强名称保护代码完整性

当我们从互联网上下载一个程序集供本地调用的时候,如何保证这个程序集是未经第三方恶意篡改过的呢?如果两个程序集的名称、大小、版本号都相同是不是就意味着这两个程序集文件就相同了呢?

.NET 平台下区分程序集采用的方法和 Win32 一样,使用名称,但是名称有强弱之分。弱名称包含版本号、程序集名称和文化。强名称在弱名称的基础上添加了数字签名。强名称的作用主要有三个:一是区分不同的程序集;二是确保代码没有被篡改过;三是在 .NET 中,只有强名称签名的程序集才能放到全局程序集缓存中。

使用强名称来包含程序集我们首先要生成用于非对称加密的密钥对,这对密钥将用于程序集的签署和验证。签署和验证的流程如图 9-3 所示。

clip_image001

9-3 签署(上)与验证(下)强名称流程

如图 9-3 所示,在进行强名称签名的时候我们首先对程序集(不包括 DOS 头和 PE 头)进行 Hash 运算,得到文件的散列值,然后使用私钥对散列值进行加密,得到密文。然后将公钥、公钥标识(对公钥进行 SHA-1 散列运算后得到的密文的最后 8 个字节)和密文三个信息保存在程序集中。在加载该程序集时,首先对该程序集进行 Hash 运算得到一个 Hash 值(我们称之为“新 Hash 值”),然后从程序集中提取公钥对密文解密得到原始的 Hash 值,如果两个 Hash 值相同,即通过验证。

对程序集签名有正常签名和延迟签名两种方式。

延迟签名是在我们开发人员只具有对公钥的访问权限而没有对私钥的访问权限时使用的。这是我们可以先将程序集编译并预留签名空间。此时的程序集无法正常运行和调试。

9.2.2.1  SDK 中创建强名称签名的程序集

对程序集进行强名称签名,我们要首先准备好密钥。在本书的第 6 章中,介绍过使用 Visual Studio SDK 中提供的强名称工具( Sn.exe )可以生成密钥对。我们使用如图 9-4 的命令生成一个新的密钥对并保存到本地文件 test.snk 中。

clip_image002

 

9-4  生成密钥文件

接下来我们新建一个控制台测试项目 StrongName ,主要代码如代码清单 9-3 所示。

代码清单 9-3  StrongName 项目主要代码

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
 
namespace StrongName
{
   
class   Program
    {
       
static   void Main( string [] args)
        {
            string aa = "";

Assembly ass = Assembly .GetExecutingAssembly();
           
Console .WriteLine(ass.FullName.ToString());
           
byte [] pKey = ass.GetName().GetPublicKey();
           
byte [] pKeyToken = ass.GetName().GetPublicKeyToken();
           
string pKeyString = GetString(pKey);
           
string pKeyTokenString = GetString(pKeyToken);
           
Console .WriteLine( " 公钥是: {0}" ,pKeyString);
           
Console .WriteLine( " 公钥标识是 {0}" , pKeyTokenString);
           
Console .Read();
        }
 
       
private   static   string GetString( byte [] bytes)
        {
           
StringBuilder sb = new   StringBuilder ();
           
foreach ( byte b in bytes)
            {
                sb.Append(
string .Format( "{0:x}" ,b));
            }
           
return sb.ToString();
        }
    }
}

代码清单 9-3 的代码很简单,使用反射来列举当前程序集的名称和使用的公钥及公钥标识。在没有对程序集进行强名称签名时我们运行程序得到如图 9-5 的结果。

clip_image002[6]

 

9-5 没有对程序集进行强名称签名时代码清单 9-3 的运行结果

下面我们在命令行对 C# 编译器( csc.exe )执行如图 9-4 所示的命令。

clip_image002[8]

 

9-4 对程序集应用强名称

现在我们再来执行刚才生成的 Program.exe ,看看结果如何,如图 9-5 所示。

clip_image002[10]

 

9-5  对程序集进行强名称签名之后代码清单 9-3 的运行结果

从图 9-5 我们可已看出,执行强名称签名之后我们成功的输出公钥和公钥标识。

为了使编译器能自动为代码进行强名称签名我们可以为代码添加特性指明强名称签名需要的密钥文件。添加特性之后的代码示例如代码清单 9-4 所示。

代码清单 9-4  使用特性进行强名称签名

using System;

using System.Reflection;

  [assembly:AssemblyKeyFileAttribute("TestKey.snk")]

代码清单 9-4 使用   AssemblyKeyFileAttribute   指定包含密钥对的文件的名称。

当我们需要对程序集进行延迟签名的时候,我们需要对   AssemblyDelaySignAttribute 特性和 AssemblyKeyFileAttribute   联合使用,形式如代码清单 9-5 所示

代码清单 9-5  对程序集延迟签名的特性声明

using System;

using System.Reflection;

    [assembly:AssemblyKeyFileAttribute("myKey.snk")]

      [assembly:AssemblyDelaySignAttribute(true)]

如代码清单 9-5 ,当我们需要对程序集延迟签名的时候,我们要指定包含公钥的文件并设定 AssemblyDelaySignAttribute 特性值为 true

9.2.2.2  VS 中创建强名称签名的程序集

SDK 中进行强名称签名未免麻烦了一些,下面我们以 VS2010 为例,讲解如何在 Visual Studio 中进行强名称签名的操作。

我们打开项目的属性,切换到签名页,如图 9-6 所示。

clip_image002[12]

 

9-6  项目的签名页

从图 9-6 中我可以看出,项目签名属性页中包含了三个大的配置项,第一个是为 ClickOnce 清单签名,第二个是为程序集签名,第三个是延迟签名。

为了使用 ClickOnce 部署发布应用程序,应用程序和部署清单必须使用公钥 / 私钥对进行强命名并使用 Authenticod 技术进行签名。可以使用 Windows 证书存储区的证书或密钥文件为清单签名。也可以创建新的测试证书。

为程序集签名的选项中,我们可以选择密钥文件或者生成新的密钥文件来对程序集进行签名。

如果勾选了仅延迟签名,那么将对程序集进行延迟签名。

如图 9-7 ,在创建了 ClickOnce 签名和程序集签名之后,项目自动添加了两个密钥文件 StrongName_TemporaryKey.pfx test.pfx

clip_image002[14]

 

9-7  创建 ClickOnce 签名和程序集签名

强名称签名的程序集如果被篡改,那么 CLR 在加载该程序集进行完整性验证的时候就会失败。我们现在使用文本编辑工具打开 StrongName.exe ,在保证不破坏 PE 文件格式的前提下对其进行简单的修改,这里我只把程序中定义的变量 aa 替换成 bb ,如图 9-8 所示。

clip_image002[16]

 

9-8 修改强名称签名的程序集

修改之后,我们再次运行 StrongName.exe ,看到程序报出的异常为强名称验证失败,入图 9-9 所示。

clip_image002[18]

 

9-9 强名称验证失败

注意

1.       强名称签名的程序集不能引用未被签名的程序集。

2.  .NET Framework 3.5 Service Pack 1 开始,当程序集载入完全信任应用程序域(例如   MyComputer   区域的默认应用程序域)中时,将不会验证强名称签名。   这称为强名称跳过功能。   在完全信任环境中,对于已签名的完全信任程序集,无论这些程序集的签名是什么,对   StrongNameIdentityPermission   的要求将总是成功。   强名称跳过功能避免了在此情况下验证完全信任程序集的强名称签名所带来的不必要开销,从而可使程序集更快加载。

该跳过功能适用于用强名称签名并具有以下特征的任何程序集:

1)   完全受信任而没有   StrongName   证据(例如,具有   MyComputer   区域证据)。

2)   加载到完全受信任的   AppDomain   中。

3)   从该   AppDomain     ApplicationBase   属性下的位置加载。

4)   未经延迟签名。

可以对单独的应用程序或整个计算机禁用此功能。  

1)   对所有应用程序禁用强名称跳过功能

32 位计算机上,在系统注册表中的 HKEY_LOCAL_MACHINE/SOFTWARE/Microsoft/.NETFramework 项下创建一个子项。 使用 DWORD 值为 0 的项名称 AllowStrongNameBypass

64 位计算机上,在系统注册表中的 HKEY_LOCAL_MACHINE/SOFTWARE/Microsoft/.NETFramework 项下创建一个子项。 使用 DWORD 值为 0 的项名称 AllowStrongNameBypass HKEY_LOCAL_MACHINE/SOFTWARE/Wow6432Node/Microsoft/.NETFramework 项下创建相同的子项。

2)   对单个应用程序禁用强名称跳过功能

打开或创建应用程序配置文件。添加下面的项:

<configuration>

 <runtime>

 < bypassTrustedAppStrongNames enabled="false" />

 </runtime>

</configuration>

可通过移除该配置文件设置或将该特性设置为“ true” 为该应用程序恢复跳过功能。

9.2.2  引用强名称签名的程序集

引用强名称程序集的过程对我们来说都是透明,我们无需做额外的工作。但是我们可以通过这种方式来检验强名称程序集的作用。

如图 9-10 我们首先创建一个类库项目 StrongNameReferenceLib ,对其进行强名称签名。

clip_image002[20]

       9-10  引用强名称程序集

接下来我们修改之前创建的 StrongName 项目,让它来引用 StrongNameReferenceLib 项目,调用其 GetHello 方法。

StrongNameReferenceLib 项目的主要代码如代码清单 9-6 所示。

代码清单 9-6 StrongNameReferenceLib 项目主要代码

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
 
namespace StrongNameReferenceLib
{
   
public   class   Class1
    {
       
public   static   string GetHello()
        {
           
return   "Hello" ;
        }
    }
}

修改后的 StrongName 项目代码如代码清单 9-7 所示。

代码清单 9-7 StrongName 项目代码

using System.Text;
using System.Reflection;
using StrongNameReferenceLib;
 
namespace StrongName
{
    
   
class   Program
    {
       
static   void Main( string [] args)
        {
         
Console .WriteLine(  Class1 .GetHello());
           
           
Console .Read();
        }
    }
}

重新编译 StrongName 项目,我们现在得到新的 StrongName.exe 文件。使用 ILDasm 打开 StrongName.exe 文件,查看它的程序集清单,如图 9-11 所示。

clip_image002[22]

 

9-11  StrongName.exe 程序集清单

  在图 9-11 中的这份程序集清单,我们可以看到它引用了两个具有强名称签名的程序集, mscorlib 和我们新创建的 StrongNameReferenceLib ,对两个程序集分别添加了版本和 publickeytoken 标识。

下面我们去除 StrongNameReferenceLib 的强名称签名,重新编译该项目,但我们并不重新编译 StrongName 项目,而用新生成的 StrongNameReferenceLib.dll 替换 StrongName.exe 之前引用的 StrongNameReferenceLib.dll ,看看会发生事情。结果如图 9-12 所示。

clip_image002[24]

 

9-12  StrongName 项目替换 dll 之后结果

我们从图 9-10 的异常信息可以看到, StrongName 项目找不到匹配的程序集。原因在于在 StrongName 的程序集清单中存储着 PublicKeyToken 值,而没有强名称签名的项目是没有该属性的。

9.2.3  强名称的脆弱性

上面的几个小节我们共同体验了强名称对程序集的保护方式和原理,但是这种保护的强度到底有多大呢?对恶意篡改者来说能得到有效的防御吗?我们先看下面的例子。

现在我们回到代码清单 9-7 ,重新对 StrongNameReferenceLib 项目进行强名称签名,然后编译 StrongName 项目。现在在 StrongName 项目的 bin 目录里有 StrongNam.exe StrongNameReferenceLib.dll 两个文件。然后使用 ILDasm 打开 StrongNameReferenceLib.dll 文件,转储为 il 文件,这里我使用记事本打开 il 文件,如图 9-13 所示。

clip_image002[26]

 

       9-13  StrongNameReferenceLib.dll IL 源码

我们在 .il 文件中找到三处代码: publickkeytoken publickey hash ,把对应的内容都删除,再重新使用 ILAsm 编译,这时该程序集的强名称就被成功的去除。

替换程序集的强名称方法基本相同。目前网络上有很多去除和替换强名称的工具,这里就不掩饰了。

那么我们应该通过什么样的方式来保护强名称不被去除或者篡改呢?这个问题我会在专门的文章里讨论。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值