如何安全的給.net程序簽名
「TestLib,Version = 1.0.0.0,Culture = neutral,PublicKeyToken = 769a8f10a7f072b4」
如果你能看懂上面這行的意思,那你很可能是一個.NET開發人員,同時你也可能知道結束處的十六進位字元串表示的是一個公鑰標記。
不錯,上面的字元串就是一組.net程序集強名稱簽名。
但你知道如何計算這個令牌嗎?你知道強名稱簽名的結構嗎?在這篇文章中,我們將詳細介紹強名稱的工作原理及其優缺點。
強名稱是由程序集的標記加上公鑰和數字簽名組成的。其中,程序集的標記包括簡單文本名稱、版本號和區域性信息(如果提供的話)。也就是說,一個完整的強名稱字元串包含4部分:
1.程序集的文件名;
2.程序集的版本號;
3.程序集的區域性信息;
4.一個公鑰以及一個數字簽名;
其中前3部分信息會存儲在程序集的清單(manifest)中。清單包含了程序集的元數據,並嵌入在程序集的某個文件中。它使用相應私鑰從程序集文件生成。
具有強名稱的程序集只能使用其他具有強名稱的程序集的類型。 否則,將會危害具有強名稱的程序集的安全性。
在.NET框架中強名稱程序集是通過公鑰和私鑰加密來產生這個唯一標記的。
因為強命名程序集使用公鑰/私鑰對來進行唯一性簽名,不同的公司公鑰/私鑰對不可能相同,所以所生成的程序也不相同,這就解決了以前老出現的DLL Hell問題(兩個不同的公司可能開發處具有相同名稱的程序集,如果將相同名稱的程序集放置到同一個目錄下,則會出現程序集覆蓋現象,最後安裝的程序集會覆蓋前面的程序集,從而可能導致應用序不能正常運行)。任何兩個強命名的程序集,就算名稱一模一樣,Windows也知道他們是兩個不同的版本,因為他們的唯一標記不一樣。並且你可以通過配置文件控制應用程序去正確的載入你想要載入的那個dll。
所以強名稱簽名還能唯一標記程序集所使用的組件,所以強名稱的作用主要有三個:
1.區分不同的程序集;
2.確保代碼沒有被篡改過;
3.在.NET中,只有強名稱簽名的程序集才能放到全局程序集緩存中。
公鑰標記和強名稱簽名
公鑰是包含在程序集元數據中的#Blob流的一部分,下圖就是dnSpy窗口的一部分,dnSpy是一款開源的基於ILSpy發展而來的.net程序集的編輯,反編譯,調試神器:
下圖列出了構建公鑰模塊的元素:
上圖中記錄有公鑰標記,開發人員和最終用戶看到的也是公鑰標記,而不是公鑰。
公鑰和公鑰標記有什麼區別呢?
公鑰總共佔160位元組,有32個位元組裝的是頭信息,另外有128個位元組裝的是數據。公鑰對程序開發者來說是可見的。因為公鑰佔160位元組,一個程序集可能會引用很多其他的程序集,所以在最終生成的文件中會有很大一部分空間被公鑰佔用,在使用的時候不太方便,於是人們提出了一個公鑰標記的概念,公鑰標記只佔8個位元組。公鑰標記是把公鑰進行哈希處理(使用SHA1演算法),把哈希處理的結果的後面8個位元組進行逆轉,得到的就是公鑰標記,
本文的測試中所用的公鑰標記為:
769a8f10a7f072b4 (SHA-1(00 24 00 00 0c 80 … c8 8a c1 b1) = 9aa4de0a96ada8d83d6d7678b472f0a7108f9a76
怎樣對程序集進行RSA簽名
1.對PE文件內容進行哈希處理,然後把哈希處理後的值,用私鑰進行RSA簽名,把簽名後的值加入到PE文件的CLR頭中去。同時也會把公鑰加入到程序集的元數據中去。
2.在生成元數據表FileRef時,CLR會把程序集裡面的文件也進行哈希處理,得到一個哈希值,把文件和對應的哈希值同時加到FileRef元數據表中,
為了計算RSA簽名,我們需要擁有與公鑰相對應的私鑰。如果你想看到實現驗證的C#代碼,請查看dnLib庫中的StrongNameSigner.cs文件。
在研究了強名稱簽名結構之後,讓我們來了解一下可以用來創建強名稱簽名的工具。我們發現生成的.snk文件會存儲RSA密鑰詳細信息(如果你對.snk文件格式的詳細信息感興趣,請查看010編輯器模板):
然後我們可以使用C#編譯器(csc.exe)或組裝鏈接器(al.exe),這兩個兩者都可以被注入/ keyfile參數,在這裡我們提供了剛生成的.snk文件的路徑,如下圖:
此命令將基於文件內容的SHA-1演算法生成簽名。不過現在,SHA-1已經不是一種安全的演算法了,強烈建議使用SHA-2演算法。
如果要使用SHA-2演算法對我們的程序集進行簽名,我們首先需要提取.snk文件的公鑰部分:
然後延遲私鑰簽名(強名稱簽名塊將被清0,強名稱簽名標誌不會被置位)。 csc.exe和al.exe都會被注入/ delaysign +參數用於延遲私鑰簽名:
csc.exe /keyfile:TestLibPubKey.snk /delaysign+ /t:library TestLib.cs
什麼是延遲簽名?
如果要生成一個強命名的程序集,那麼每次生成都需要進行簽名,在開發的時候就會頻繁的訪問私鑰文件,而私鑰文件一般都是非常保密的,想要頻繁使用可能有些費事。所以就有了延遲簽名的機制。延遲簽名的意思就是在開發階段,只把公鑰提供給開發人員,只有公鑰對程序集進行簽名,在最後打包發布的時候,才使用私鑰來進行簽名。
最後,我們需要使用私鑰重新對程序集進行簽名:
Authenticode 簽名
Authenticode 簽名,顧名思義,用於驗證程序集的開發者身份另外它還能保護組件的完整性。Authenticode 簽名的大小和位置存儲在PE可選標題中:
強名稱雖然引入了「身份」的概念,但沒有包括「信任」機制。例如,使用強名稱簽署的一個程序集雖然能保證版本兼容性,但不能保證要載入的程序集來自Quilogy。為了用Authenticode數字簽名來簽署程序集,開發者要使用.NET框架配套提供的命令行實用程序Signcode.exe。程序集使用Authenticode簽名進行簽署之後,管理員就可創建相應的策略,利用代碼訪問安全性(CAS)機制,允許它下載到用戶的機器上並進行載入。簽名將成為CLR的類載入器所使用的身份憑證的一部分,用於判斷程序集是否應該載入。
程序集可攜帶完整的 Microsoft Authenticode 簽名。 Authenticode 簽名包括建立信任的證書。
請務必注意強名稱不要求代碼以這種方式進行簽名。事實上,用於生成強名稱簽名的密鑰不需要與用於生成Authenticode簽名的密鑰相同。
跳過受信任程序集的簽名驗證
從.NET Framework 3.5 Service Pack 1開始,當程序集載入到完全信任的應用程序域(如 MyComputer 區域的默認應用程序域)時,不會驗證強名稱簽名。 這被稱之為強名稱跳過功能。 在完全信任的環境中,對於已簽名的完全信任的程序集,對StrongNameIdentityPermission的需求總是成功,而不考慮其簽名。 這種情況下,強名稱跳過功能可避免完全信任程序集不必要的強名稱簽名驗證開銷,允許更快地載入程序集。
跳過功能適用於使用強名稱進行簽名及具有以下特徵的任何程序集:
1.完全受信任,無需 StrongName證據(如具有 MyComputer 區域證據);
2.載入到完全受信任的 AppDomain;
3.載入自該 AppDomain 的 ApplicationBase 屬性下的某個位置;
4.簽名沒有延遲;
總結
由於很容易跳過簽名驗證,你可能懷疑二進位文件的整個結構。不過,考慮一下,如果惡意攻擊者獲得對二進位文件的訪問權,簽名將不能保護你的軟體安全。不過,簽名程序集是唯一的能證明程序集中的代碼是合法的證明,並且沒有被篡改。
我們應該在客戶端接收到二進位文件(這通常由安裝程序完成)後執行第一次驗證,以確保在傳輸過程中沒有篡改;接下來,在二進位文件所在的文件夾中設置有效的訪問許可權非常重要,只有被授權的人才能修改文件;最後,每當有我們的應用程序出現問題,我們應該要求客戶端在填寫錯誤報告之前驗證簽名,只有這樣我們才能確定該錯誤是我們本來應該有的真實錯誤。
TAG:嘶吼RoarTalk |