如何設計簽署的XML檔案 (XML Signature)
最近因為專案上的需求,需要使用服務 (Web Service/Web API) 來交換簽署過的 XML 資料,因為為了保證資料沒有被竄改,因此可透過 XML Signature 來簽署 XML 資料,如過使用非對稱來加密 XML 資料,那麼對方就一定得用私密的金鑰組來驗證此 XML 沒有遭到竄改。
首先,我們先來了解什麼是 XML Signature?
XML Signature
也稱作XMLDsig,XML-DSig,XML-Sig,是一個定義數字簽名的XML語法的W3C建議的標準。透過XML signature可以用來簽名任何類型的資源,最常見的是XML文件檔,但是任何可以通過URL訪問的資源都可以被簽名。
XML Signature 結構
含一個 Signature 元素,是一個以 Signature 為根的一個 XML 結構,其中包含了最重要的標籤 SignedInfo,定義了如何「圈選」要簽署的資料區塊,如何進行資料轉換與計算摘要等,以其最後如何簽章的訊息。而 SignatureValue 則放置實際上簽章的簽章值。KeyInfo是描述與簽署此份文件的金鑰訊息。而 Object 的標籤則是一個活用的欄位,可以描述資料的來源或描述簽章其他相關訊息 SignatureProperty,如Time Stamp 等等... 其名字空間為 http://www.w3.org/2000/09/xmldsig#。
基本結構如下所示:
XML 簽章的類型
XML簽章可以分成三種類型:
- Enveloping Signatures
- Enveloped Signatures
- Detached Signatures
其中主要差異為,Enveloping Signatures 與 Enveloped Signatures 是將 XML 資料與簽章放置在同一個 XML 文件檔案。而 Detached Signatures 顧名思義就是簽章的內容與欲簽署的 XML 資料是放置在不同的 XML 文件檔案內。
何謂 XML 正規化?
為何需要做 XML 正規化呢?這是由於XML簽章簽章的對象主要是 XML 資料內容的本身,並不是使用資料的二進位碼。例如:我們簽署一個如:
<測試根結點>
<測試 >
<子測試>Toyota Corolla Altis 2014</子測試>
</測試 >
</測試根結點>
類似這樣一個簡單的XML,如果使用傳統的簽章方式 (PKCS#7) 則會將這一段文字包含空白轉換為二進位碼,在對此二進位碼進行簽章。因此,在 XML 的標簽中若有空白,如下:
那麼會被識別是不同的 XML,誤以為被串改,所以才需要做 XML 正規化。
另外如果編碼不同,比如來源是 UTF-8,但目的以 big5 去解簽章這樣也是不行的。
註:
如需 XML 加密標準的詳細資訊,請參閱全球資訊網協會 (W3C) 的 XML 加密規格,網址為 http://www.w3.org/TR/xmldsig-core/。
以 C# 來實做 XML Signature
如何以 C# 來實做 XML Signature 呢?在.NET Framework 中的 System.Security.Cryptography.Xml 命名空間中即支援各種簽署 XML 相關的類別,以數位簽章簽署 XML 文件或部分 XML 文件。
再開始之前,首先,先建立一個簡單的 Web Service ,裡面的 Web Method 只傳回一個簡單的類別:
1: [WebMethod]
2: public Balloon GetChangedXML()
3: {
4: return new Balloon()
5: {
6: MyBalloonChilder = new BalloonChilder()
7: {
8: CarName = "Toyota Corolla Altis 2014"
9: }
10: };
11: }
回傳的 Balloon 類別定義如下:
1: public class BalloonChilder
2: {
3: [XmlElement(ElementName = "子測試")]
4: public string CarName
5: {
6: get { return this._carName; }
7: set { this._carName = value; }
8: }
9: private string _carName { get; set; }
10: }
11: [XmlRoot(ElementName="測試根結點")]
12: public class Balloon
13: {
14: private BalloonChilder _myBalloonChilder;
15: [XmlElement(ElementName="測試")]
16: public BalloonChilder MyBalloonChilder
17: {
18: get { return this._myBalloonChilder; }
19: set { this._myBalloonChilder = value; }
20: }
21: }
這個 Web Service 回傳結果如下:
如上的 XML 文件還未加上 XML Signature 數位簽章,透過加簽的方式 XML Signature (一般稱為 XMLDSIG) 可讓您確保資料在簽署之後並未遭到修改。
下面示範如何建立 RSA 簽署金鑰,將此金鑰加入至安全金鑰容器,然後利用金鑰來數位簽署 XML 文件數位簽署整份 XML 文件,並將簽章附加到 <Signature> 項目的文件中。
一、建立密碼編譯計算之密碼編譯服務供應者Cryptography Service Provider (CSP)
1: CspParameters cspParams = new CspParameters();
2: cspParams.KeyContainerName = "GELIS_RSA_KEY";
二、使用 RSACryptoServiceProvider 類別產生非對稱金鑰
1: RSACryptoServiceProvider rsaKey = new RSACryptoServiceProvider(cspParams);
三、將原本的 Web Method 的程式碼改為如下就可對XML文件進行簽署
1: [WebMethod]
2: public Balloon GetChangedXML()
3: {
4: CspParameters cspParams = new CspParameters();
5: cspParams.KeyContainerName = "GELIS_RSA_KEY";
6:
7: // 建立一個新的 RSA key 並儲存至 RSACryptoServiceProvider 的容器中.
8: RSACryptoServiceProvider rsaKey = new RSACryptoServiceProvider(cspParams);
9:
10: Balloon balloon = new Balloon()
11: {
12: MyBalloonChilder = new BalloonChilder()
13: {
14: CarName = "Toyota Corolla Altis 2014"
15: }
16: };
17:
18: XmlDocument xmlDoc = new XmlDocument();
19:
20: xmlDoc.PreserveWhitespace = true;
21:
22: string XmlString = XmlHelper.SerializeAnObject(balloon);
23:
24: xmlDoc.LoadXml(XmlString);
25: XmlHelper.SignXml(xmlDoc, rsaKey);
26:
27: Balloon result = XmlHelper.DeSerializeAnObject<Balloon>(xmlDoc.InnerXml);
28:
29: return result;
30: }
如上程式碼,你可以在 IE 的回傳中看見 XML Signature 的區段如下:
如上,基本的 XML Signature 應用。下次筆者介紹如何驗證 XML 是否有被竄改。
註:
剛忘了說明,如果要在序列化輸出的 XML 文件中有 <Signature> 的區段,得在 Balloon 類別中宣告 Signature 屬性,並指定命名空間為http://www.w3.org/2000/09/xmldsig#,以及自行宣告 Signature 類別所需相屬性及類別。
簽名:
學習是一趟奇妙的旅程
這當中,有辛苦、有心酸、也有成果。有時也會有瓶頸。要能夠繼續勇往直前就必須保有一顆最熱誠的心。
軟體開發之路(FB 社團):https://www.facebook.com/groups/361804473860062/
Gelis 程式設計訓練營(粉絲團):https://www.facebook.com/gelis.dev.learning/
如果文章對您有用,幫我點一下讚,或是點一下『我要推薦』,這會讓我更有動力的為各位讀者撰寫下一篇文章。
非常謝謝各位的支持與愛護,小弟在此位各位說聲謝謝!!! ^_^