强名称(1)使用强名称保护代码完整性
强名称是由程序集的标识加上公钥和数字签名组成的。其中,程序集的标识包括简单文本名称、版本号和区域性信息(如果提供的话)。强名称是使用相应的私钥,通过程序集文件(包含程序集清单的文件,并因而也包含构成该程序集的所有文件的名称和散列)生成的。Microsoft® Visual Studio® .NET 和在 .NET Framework SDK 中提供的其他开发工具能够将强名称分配给一个程序集。强名称相同的程序集应该是相同的。
通过签发具有强名称的程序集可以确保名称的全局唯一性。强名称满足以下要求:
1) 强名称依赖于唯一的密钥对来确保名称的唯一性。程序集名称不会与任何其他程序集名称相同,因为用一个私钥生成的程序集的名称与用其他私钥生成的程序集的名称不相同。
2) 强名称保护程序集的版本沿袭。强名称可以确保没有人能够生成程序集的后续版本。用户可以确信,他们所加载的程序集的版本出自创建该版本(应用程序是用该版本生成的)的同一个发行者。
强名称提供可靠的完整性检查。通过.NET Framework 安全检查后,即可确信程序集的内容在生成后未被更改过。但请注意,强名称中(或强名称本身)并不暗含信任级别,例如由数字签名和支持证书提供的信任。使用强名称保护代码完整性虽然强名称是.NET加密领域的元老,也是微软推荐的应用程序保护机制,但是由于托管程序可以被反汇编成IL代码,更改或者去除强名称也就成了可能。强名称的保护强度也因此大大减弱。
当从互联网上下载一个程序集供本地调用的时候,如何保证这个程序集是未经第三方恶意篡改过的呢?如果两个程序集的名称、大小、版本号都相同,是不是就意味着这两个程序集文件就相同了呢?
在.NET平台下区分程序集采用的方法和Win32一样,使用名称,但是名称有强弱之分。弱名称包含版本号、程序集名称和文化。强名称在弱名称的基础上添加了数字签名。强名称的作用主要有三个:
q 区分不同的程序集;
q 确保代码没有被篡改过;
q 在.NET中,只有强名称签名的程序集才能放到全局程序集缓存中。
使用强名称来包含程序集首先要生成用于非对称加密的密钥对,这对密钥将用于程序集的签署和验证。签署和验证的流程如图9-7所示。
图9-7 签署(上)与验证(下)强名称流程
如图9-7所示,在进行强名称签名的时候,首先对程序集(不包括DOS头和PE头)进行Hash运算,得到文件的散列值;然后使用私钥对散列值进行加密,得到密文。将公钥、公钥标识(对公钥进行SHA-1散列运算后得到的密文的最后8个字节)和密文三个信息保存在程序集中。在加载该程序集时,首先对该程序集进行Hash运算得到一个Hash值(称为“新Hash值”),然后从程序集中提取公钥,对密文解密得到原始的Hash值,如果两个Hash值相同,即通过验证。
对程序集签名有正常签名和延迟签名两种方式。延迟签名是在开发人员只具有对公钥的访问权限,而没有对私钥的访问权限时使用的。这时可以先将程序集编译并预留签名空间。此时的程序集无法正常运行和调试。
在SDK中创建强名称签名的程序集
对程序集进行强名称签名,首先准备好密钥。本书的第6章介绍过使用Visual Studio SDK中提供的强名称工具(Sn.exe)可以生成密钥对。使用如图9-8所示的命令生成一个新的密钥对,并保存到本地文件test.snk中。
图9-8生成密钥文件
接下来新建一个控制台测试项目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-9所示的结果。
图9-9 没有对程序集进行强名称签名时代码清单9-3的运行结果
下面在命令行对C#编译器(csc.exe)执行如图9-10所示的命令。
图9-10 对程序集应用强名称
现在再来执行刚才生成的Program.exe,看看结果如何,如图9-11所示。
图9-11对程序集进行强名称签名之后代码清单9-3的运行结果
从图9-11已以看出,执行强名称签名之后,成功输出公钥和公钥标识。
为了使编译器能自动为代码进行强名称签名,可以为代码添加特性指明强名称签名需要的密钥文件。添加特性之后的代码示例如代码清单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。
在VS中创建强名称签名的程序集
在SDK中进行强名称签名未免麻烦了一些,下面以VS 2010为例,讲解如何在Visual Studio中进行强名称签名的操作。
打开项目的属性,切换到签名页,如图9-12所示。
图9-12项目的签名页
从图9-12中可以看出,项目签名属性页中包含了三大配置项,第一个是“为ClickOnce清单签名”,第二个是“为程序集签名”,第三个是“仅延迟签名”。
为了使用ClickOnce部署发布应用程序,应用程序和部署清单必须使用公钥/私钥对进行强命名,并使用Authenticode 技术进行签名。可以使用Windows证书存储区的证书或密钥文件为清单签名,也可以创建新的测试证书。
为程序集签名的选项中,可以选择密钥文件或者生成新的密钥文件来对程序集进行签名。
如果勾选了“仅延迟签名”,那么将对程序集进行延迟签名。
如图9-13所示,在创建ClickOnce签名和程序集签名之后,项目自动添加了两个密钥文件:StrongName_TemporaryKey.pfx和test.pfx。
图9-13 创建ClickOnce签名和程序集签名
强名称签名的程序集如果被篡改,CLR在加载该程序集进行完整性验证的时候就会失败。现在使用文本编辑工具打开StrongName.exe,在保证不破坏PE文件格式的前提下对其进行简单的修改,这里只把程序中定义的变量aa替换成bb,如图9-14所示。
如图9-14修改强名称签名的程序集
修改之后,再次运行StrongName.exe,看到程序在控制台上输出的异常信息为“强名称验证失败”,如图9-15所示。
图9-15强名称验证失败
注意:
1) 强名称签名的程序集不能引用未被签名的程序集。
2) 从 .NET Framework 3.5 版Service Pack 1开始,当程序集载入完全信任应用程序域(例如MyComputer 区域的默认应用程序域)中时,将不会验证强名称签名,这称为强名称跳过功能。 在完全信任环境中,对于已签名的完全信任程序集,无论这些程序集的签名是什么,对 StrongNameIdentityPermission 的要求将总是成功。 强名称跳过功能避免了在此情况下验证完全信任程序集的强名称签名所带来的不必要开销,从而可使程序集更快加载。
该跳过功能适用于用强名称签名并具有以下特征的任何程序集:
q 完全受信任而没有 StrongName 证据(例如具有 MyComputer 区域证据)。
q 加载到完全受信任的 AppDomain 中。
q 从该 AppDomain 的 ApplicationBase 属性下的位置加载。
q 未经延迟签名。
可以对单独的应用程序或整个计算机禁用此功能。
对所有应用程序禁用强名称跳过功能。
在 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 项下创建相同的子项。
对单个应用程序禁用强名称跳过功能。
打开或创建应用程序配置文件。添加下面的项:
<configuration>
<runtime>
< bypassTrustedAppStrongNames enabled="false" />
</runtime>
</configuration>
可通过移除该配置文件设置或将该特性设置为“true”为该应用程序恢复跳过功能。
-----------------------------------注:本文部分内容改编自《.NET 安全揭秘》