1. 程式人生 > >.NET中的安全性之數字簽名、數字證書、強簽名程式集、反編譯

.NET中的安全性之數字簽名、數字證書、強簽名程式集、反編譯


本文將探討數字簽名、數字證書、強簽名程式集、反編譯等以及它們在.NET中的運用(一些概念並不侷限於.NET在其它技術、平臺中也存在)。

1.數字簽名

數字簽名又稱為公鑰數字簽名,或者電子簽章等,它藉助公鑰加密技術實現。數字簽名技術主要涉及公鑰、私鑰、非對稱加密演算法。

1.1公鑰與私鑰

公鑰是公開的鑰匙,私鑰則是與公鑰匹配的嚴格保護的私有金鑰;私鑰加密的資訊只有公鑰可以解開,反之亦然。

在Visual Studio中,可以在專案屬性頁或者通過強命名工具Sn.exe生成公鑰與私鑰對,參考建立公鑰私鑰對

1.2非對稱加密演算法

非對稱加密演算法(也叫公鑰加密演算法)藉助於私鑰加密、公鑰解密,使用了兩個不同的金鑰,因此被稱為非對稱加密;數字簽名屬於非對稱加密演算法的一種。比較著名的非對稱加密演算法是RSA,在.NET中對應的類為NET中的RSACryptoServiceProvider類包含這些相應的方法。

1.3數字簽名過程

訊息的傳送者用一個HASH函式提取出訊息的摘要,然後用私鑰藉助非對稱加密演算法對訊息進行加密,將訊息原文與加密後的數字簽名同時傳送給接受者;訊息接受者收到訊息原文與數字簽名後,首先利用同一HASH函式從訊息原文提取訊息摘要,再用傳送者釋出的公鑰對數字簽名進行解密出發送者的訊息摘要,對比訊息摘要,如果一致則證明,此訊息來自發送者,且內容未經修改。

對於RSA演算法,解密與解密分別對應:EncryptDecrypt方法。

1.4數字簽名的功能

確定訊息確實是由傳送方簽名併發出來的,因為私鑰是私有的,其他人假冒不了傳送方的簽名。二是數字簽名能確定訊息的完整性。因為數字簽名的特點是它代表了訊息的特徵,訊息如果發生改變,HASH函式得到的訊息摘要就會不同,數字簽名的值也將發生變化,因而不同的訊息將得到不同的數字簽名。

2.數字證書

數字證書亦稱電子證書,它是在Internet上用來標誌和證明網路通訊雙方身份的數字資訊檔案。它是一個經證書授權中心數字簽名的包含公開金鑰擁有者資訊以及公開金鑰的檔案。最簡單的證書包含一個公開金鑰、名稱以及證書授權中心的數字簽名。授權中心的數字簽名可以幫助我們從授權中心驗證證書是否被更改,即身份資訊是否被更改等。簡言之它就如同我們的身份證。

數字證書可以保證資訊除傳送方和接收方外不被其它人竊取;資訊在傳輸過程中不被篡改;傳送方能夠通過數字證書來確認接收方的身份;傳送方對於自己的資訊不能抵賴。

數字證書廣泛運用於伺服器證書,如SSL證書,電子郵件證書,客戶端證書等等。

理論上任何人都可以給你發個數字證書,亦即是說給你發數字證書的那個人或機構對你的公鑰進行加簽。對PGP和GPG系統來說,就是如此,而不需要一個統一的身份認證機構。但我們一般僅信任權威的數字認證機構頒發的證書。

在Internet Properties->內容->證書 可以檢視已經安裝在本機的證書:


上圖選中項為阿里巴巴的證書的公鑰。

3.強簽名程式集

3.1強簽名程式集簡介

強名稱是由程式集的標識加上公鑰和數字簽名組成的。其中,程式集的標識包括簡單文字名稱、版本號和區域性資訊(如果提供的話)。強名稱是使用相應的私鑰, 通過程式集檔案(包含程式集清單的檔案,並因而也包含構成該程式集的所有檔案的名稱和雜湊)生成的。Microsoft® Visual Studio® .NET 和在 .NET Framework SDK 中提供的其他開發工具能夠將強名稱分配給一個程式集。強名稱相同的程式集應該是相同的。
3.2強名稱程式集的功能

強名稱依賴於唯一的金鑰對來確保名稱的唯一性,只有強名稱的程式集可以加入到GAC中;強名稱保護程式集的版本沿襲;強名稱提供可靠的完整性檢查。強名稱就如同證書一樣,幫助兩個程式集之間建立信任關係。

3.3強名稱程式集的實現方案

請參看:

強名稱方案

3.4延遲程式集簽名

4.強簽名程式集實踐

4.1新建測試專案StrongNameAssembly,為其新增一個類TestClass

namespace StrongNameAssembly

{
    public class TestClass
    {
    }

}
4.2在專案屬性的簽名選項卡設定強簽名

生成專案後,用.NET Reflector開啟程式集,我們可以發現,程式集的的名稱包含了公鑰標誌:

// Assembly StrongNameAssembly, Version 1.0.0.0
Location: D:\Raymond's Documents\Visual Studio  2005\Projects\StrongNameAssembly\StrongNameAssembly\bin\Debug\StrongNameAssembly.dll
Name: StrongNameAssembly, Version=1.0.0.0, Culture=neutral, PublicKeyToken=79dbc45bc8054239
Type: Library

如果不進行強簽名,那麼公鑰標誌為null。

4.3新建測試專案ConsoleApplication1

新增對StrongNameAssembly的引用,並且更改Program.cs內容為:

using System;
using System.Collections.Generic;
using System.Text;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.Write(new StrongNameAssembly.TestClass().ToString());
            Console.ReadLine();
        }
    }
}

生成解決方案,用Reflector開啟控制檯應用程式,檢視其Reference中包含了對StrongNameAssembly的完整引用:

// Assembly Reference StrongNameAssembly
Version: 1.0.0.0
Name: StrongNameAssembly, Version=1.0.0.0, Culture=neutral, PublicKeyToken=79dbc45bc8054239

執行解決方案:


4.4更改強簽名程式集的金鑰為新的簽名金鑰或者取消強簽名而不改變ConsoleApplication1

隨意修改了強簽名的金鑰,檢視到公鑰標識變為

// Assembly StrongNameAssembly, Version 1.0.0.0
Location: D:\Raymond's Documents\Visual Studio  2005\Projects\StrongNameAssembly\StrongNameAssembly\bin\Debug\StrongNameAssembly.dll
Name: StrongNameAssembly, Version=1.0.0.0, Culture=neutral, PublicKeyToken=b03b8e071332d29f
Type: Library

直接將程式集複製到ConsoleApplication1執行目錄下,如Debug目錄下,執行會得到錯誤提示,未能載入程式集trongNameAssembly, Version=1.0.0.0, Culture=neutral, PublicKeyToken=79dbc45bc8054239

4.5修改ConsoleApplication1.exe的應用程式清單

用Reflector或者IL DASM可以檢視到ConsoleApplication1.exe的清單Manifest中,引用StrongNameAssembly的公鑰標識依然為之前的版本公鑰標識:

.assemblyexternStrongNameAssembly
{
.ver 1:0:0:0
.publickeytoken = (79DBC45BC8054239)
}

如果我們修改此標識為新的金鑰簽名的公鑰標識(b03b8e071332d29f)會是怎樣呢?

用IL DASM開啟ConsoleApplication1.exe,轉儲為.il檔案,用記事本開啟:


紅色框注部分則為需要修改的地方,修改為:

.assembly extern /*23000002*/ StrongNameAssembly
{
  .publickeytoken = (b0 3b 8e 07 13 32 d2 9f )                         // y..[..B9
  .ver 1:0:0:0
}

儲存,並且用重新編譯為應用程式(參考MSIL彙編程式):

ilasm /resource=ConsoleApplication1.res ConsoleApplication1.IL /exe
@pause


如上圖編譯成功。

執行也成功:


在Reflector中也可以看到修改後的引用:

// Assembly Reference StrongNameAssembly
Version: 1.0.0.0
Name: StrongNameAssembly, Version=1.0.0.0, Culture=neutral, PublicKeyToken=b03b8e071332d29f

由此可以看到,通過強名稱對應用程式集進行保護是很脆弱的,或者說沒有太大的用途;往往我們還需要藉助程式碼混淆來實現原始碼保護,而此話題不在本文討論範圍之內

5.幾點補充

在強命名方案一文中,有提到開發環境或工具使用開發人員私鑰對包含程式集清單的檔案雜湊簽名。該數字簽名儲存在包含程式集的清單的可移植可執行 (PE) 檔案中。這一點通過IL DASM可以檢視其公鑰、HASH演算法、以及公鑰標誌:

.assembly extern mscorlib
{
.publickeytoken = (B7 7A 5C 56 19 34 E0 89 )                         // .z\V.4..
  .ver 2:0:0:0
}

.publickey = (00 24 00 00 04 80 00 00 94 00 00 00 06 02 00 00   // .$..............
                00 24 00 00 52 53 41 31 00 04 00 00 01 00 01 00   // .$..RSA1........
                63 AE 3B 85 F4 22 94 FF 18 F8 7C 9D 4F 74 79 CA   // c.;.."....|.Oty.
                21 32 78 FA CD 10 E7 F3 68 D8 00 97 C4 CF 4D F8   // !2x.....h.....M.
                E1 00 2D E8 55 B5 51 E8 BA 91 82 9D 3E DA 61 8A   // ..-.U.Q.....>.a.
                7B 4E A0 FB 2D 1F BF 5F 7D 17 9A A1 3A A4 2C F7   // {N..-.._}...:.,.
                30 FA BC 73 ED 39 B1 64 B9 15 74 E7 25 89 8D B8   // 0..s.9.d..t.%...
                07 94 C5 78 E5 2C 22 C6 A2 8D D2 64 4D 2B 18 B2   // ...x.,"....dM+..
                39 26 18 DE 33 71 8E B5 87 C1 59 E5 2F 83 A0 8C   // 9&..3q....Y./...
                A5 EF B9 FC 83 12 D8 0A 1D 6A 91 BD 3D 81 1F D3 ) // .........j..=...
  .hash algorithm 0x00008004
  .ver 1:0:0:0
而以上程式碼為IL格式即為EXE或者DLL格式的PE檔案反編譯為IL後的內容,它需要在我們的.NET環境CLR中才可以被解釋執行;在其CLR Header中也定義了.NET的版本:

// ----- CLR Header:
// Header size:                        0x00000048
// Major runtime version:              0x0002
// Minor runtime version:              0x0005
// 0x00002074 [0x00000558] address [size] of Metadata Directory:       
// Flags:                              0x00000001
// Entry point token:                  0x06000001
// 0x00000000 [0x00000000] address [size] of Resources Directory:      
// 0x00000000 [0x00000000] address [size] of Strong Name Signature:    
// 0x00000000 [0x00000000] address [size] of CodeManager Table:        
// 0x00000000 [0x00000000] address [size] of VTableFixups Directory:   
// 0x00000000 [0x00000000] address [size] of Export Address Table:     
// 0x00000000 [0x00000000] address [size] of Precompile Header: