強名稱是由程序集的標識加上公鑰和數字簽名組成的。其中,程序集的標識包括簡單文本名稱、版本號和區域性信息(如果提供的話)。強名稱是使用相應的私鑰,通過程序集文件(包含程序集清單的文件,並因而也包含構成該程序集的所有文件的名稱和散列)生成的。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修改強名稱簽名的程序集
查看本欄目