原文地址爲:http://www.anqn.com/java/2009-05-21/a09110470.shtml
1. 技術背景
1.1 數字簽名簡介
數字簽名是非對稱密鑰技術的一種應用模式,用於保證報文的完整性,不可否認性,以及提供身份認證信息。數字簽名的原理如圖 1 所示。
圖 1:數字簽名的原理
發送者在發送報文之前,先選用某種摘要算法爲報文生成一個摘要值,並使用自己的私鑰對摘要值加密,然後將加密後的摘要附在報文後面,一同發送給報文的接收者。接收者收到報文後,從中分離出原始報文和加密後的報文摘要,使用與發送者相同的摘要算法計算原始報文的摘要值 D
,並使用發送者的公共密鑰將加密後的報文摘要解密得到摘要值 D’
,檢查 D
與 D’
是否匹配。
如果匹配,那麼由於密鑰對的唯一性,所以可以確定報文發送者的身份,而且由於數據摘要算法的特點,還可以確定原始報文在傳輸過程中沒有被篡改。
1.2 XML 數字簽名簡介
XML 發展至今,已經逐漸成爲標準的數據描述技術,在分佈式應用中廣泛地用於數據的交換。由於 XML 數據本身的特殊性和使用 XML 進行數據傳輸的分佈式應用的特點,在對 XML 文檔的特定部分進行簽名,多方簽名,以及簽名後保持 XML 文檔原有的良構特性等諸多方面,傳統的數字簽名技術都無法很好地實現。
基於這樣的問題,W3C 組織制訂了 XML 數字簽名規範,規定了標準的 XML 數字簽名語法和處理規則。同傳統意義的數字簽名相比,XML 數字簽名能夠對 XML 文檔進行細粒度地分析,支持多種方式的文檔數據轉換,只對文檔的特定部分進行簽名和驗證,並且能夠保持 XML 文檔的良構特性。此外,XML 數字簽名提供的密鑰信息表示方法清晰易讀,更加便於簽名的自動驗證處理。
1.3 XML 數字簽名實例
本節用一個簡單的例子來介紹 XML 數字簽名的語法和處理規則。
表 1:簽名前的 XML 文檔
表 1 中的 XML 文檔描述了 Peter 的信用卡支付記錄。在按照 XML 數字簽名規範對整個文檔簽名之後,生成的 XML 文檔如表 2 所示:
表 2: 簽名後的 XML 文檔
所有與 XML 數字簽名相關的信息都存放在 <Signature> 元素中。
<Signature> 元素包含有幾個主要的子元素:
<Reference> 元素至少包含一個 <Reference> 元素,每個 <Reference> 元素用於對待簽名數據進行引用,包含有引用方式、轉換方法、摘要算法和摘要值等信息。<Reference> 還包含有 XML 數據的規則化方法,並指定了數字簽名所使用的算法。
<SignatureValue> 元素包含對 <Reference> 元素規範化後的內容進行簽名生成的數字簽名的值。
<KeyInfo> 元素用於指定驗證簽名所需的公共密鑰相關信息。
XML 數字簽名的過程大致爲:
1. 根據每個 <Reference> 元素中指定的資源引用方式,摘要算法,數據轉換方法等信息,對引用資源進行轉換,然後對轉換後的結果計算出摘要值。
2. 根據 <SignedInfo> 元素中指定的 XML 數據的規範化方法對 <SignedInfo> 規則化,對規範化之後的數據生成摘要值,並使用私鑰對摘要值進行加密,將生成的加密摘要值存放在 <SignatureValue> 元素中。
1.3 XML 數字簽名的驗證
XML 數字簽名的驗證主要包括兩個步驟,首先需要對 <SignedInfo> 元素中包含的數據引用部分進行驗證,然後對整個 <SignedInfo> 元素的簽名值進行驗證。其間任何一步驗證失敗則代表整個 XML 數字簽名驗證失敗。
1. 對數據引用的驗證
對 <SignedInfo> 中每一個 <Reference> 執行如下驗證步驟:
1) 應用指定的數據轉換方法取得引用的數據對象
2) 使用指定的摘要生成算法生成摘要值
3) 將生成的摘要值同 <Reference> 中 <DigestValue> 元素包含的摘要值相比較,如果不匹配,則驗證失敗。
2. 對 <SignedInfo> 簽名值的驗證:
1) 從 <KeyInfo> 元素中的 <KeyValue> 元素或者根據 <KeyInfo> 元素中指定的信息從外部獲取用於驗證數字簽名的數據發送方公共密鑰。
2) 使用驗證密鑰將 <SignatureValue> 元素中的加密簽名值解密,得到值 D
3) 使用 <SignatureMethod> 元素指定的簽名算法對規則化之後的 <SignedInfo> 元素計算摘要值,得到值 D’
4) 判斷 D 和 D’ 是否匹配,如果不匹配,則驗證失敗。
2. XML數字簽名的Java實現
在 W3C 推出 XML 數字簽名規範之後不久,很多組織和廠商就已經開始提供實現產品。目前,除了各大廠商推出的實現產品之外,應用比較廣泛的開源產品是 Apache XML Security 項目。該項目實現了 W3C 的 XML 數字簽名規範和 XML 加密規範,並且提供 Java 和 C++ 兩個版本供用戶選用。
JSR 105 (Java XML Digital Signature API Specification) 規定了 XML 數字簽名規範的標準 Java 實現接口,於 2005 年 6 月 24 日最終發佈。隨後,於 2006 年秋季發佈的 Java SE 6 (產品代號 Mustang) 將 JSR105 納入 Java 標準庫中,爲基於 Java 的上層應用提供標準的 XML 數字簽名支持。從此,需要使用 XML 安全特性的 Java 項目有了來自 Java 核心平臺的基礎支持,再也不需要爲選擇合適的第三方產品而煩惱。
3. 使用 Java SE 6 生成 XML 數字簽名並驗證
本節使用具體的程序例子介紹如何使用 Java SE 6中的標準 Java 接口生成各種格式的 XML 數字簽名並進行驗證。所有的程序例子都使用下表中的 XML 文檔,其中主要包含有 Simon 和 Peter 二人的信用卡支付記錄。
表 3: 程序中使用的 XML 文檔
本節的程序基於 Java SE 6,請讀者自行下載安裝並配置開發環境。
3.1 生成並驗證 Enveloped 格式的 XML 數字簽名
Enveloped 格式的簽名指簽名元素包含於被簽名數據中,如表 4 所示:
表 3: Enveloped 格式的 XML 數字簽名
3.1.1 生成簽名
1. 創建 XMLSignatureFactory 實例
XMLSignatureFactory 是與簽名相關的 XML 元素對象的創建工廠。本文在這裏創建以DOM 處理機制實現的 XMLSignatureFactory 實例:
2. 創建對整個 XML 文檔的引用
這一步創建 <Reference> 元素,引用整個 XML 文檔:
創建 Reference 的時候將 URI 參數指定爲 "" 表示對整個 XML 文檔進行引用;摘要算法指定爲 SHA1;這裏將轉換方式指定爲 ENVELOPED ,這樣在對整個文檔進行引用並生成摘要值的時候,<Signature> 元素不會被計算在內。
3. 創建 <SignedInfo>
元素
<Reference>
元素創建好之後,下一步是創建 <SignedInfo>
元素:
因爲最終的數字簽名是針對 <SignedInfo>
元素而生成的,所以需要指定該 XML 元素的規範化方法,以確定最終被處理的數據。這裏指定爲 INCLUSIVE_WITH_COMMENTS
, 表示在規範化 XML 內容的時候會將 XML 註釋也包含在內。
至此,待簽名的內容(<SignedInfo>
元素)已指定好,再只需要簽名所使用的密鑰就可以創建數字簽名了。
4. 創建密鑰對
XML 數字簽名規範規定了多種在 <KeyInfo>
中指定驗證密鑰的方式,比如 <KeyName>
,<KeyValue>
,<X509Data>
,<PGPData>
等等。這裏使用 XML 數字簽名規範規定必須實現的 <DSAKeyValue>
來指定驗證簽名所需的公共密鑰。在程序中使用 java.security
包生成 DSA 密鑰對。
首先創建密鑰對:
然後以公鑰爲參數創建 <KeyValue>
元素:
根據創建好的 <KeyValue>
元素創建 <KeyInfo>
元素:
這裏創建的密鑰對,其中的公鑰已經用於創建 <KeyInfo>
元素並存放在其中,供簽名驗證使用,而其中的私鑰則會在下一步被用於生成簽名。
5. 創建 <Signature>
元素
前面已經創建好 <SignedInfo>
和 <KeyInfo>
元素,爲了生成最終的數字簽名,需要根據這兩個元素先創建 <Signature>
元素,然後進行簽名,創建出 <SignatureValue>
元素。
XMLSignature 類中的 sign
方法用於對文檔進行簽名,在調用 sign
方法之前,還需要創建 DOMSignContext
對象,爲方法調用提供上下文信息,包括簽名所使用的私鑰和最後生成的 <Signature>
元素所在的目標父元素:
這裏首先使用 JAXP 的 DOM 接口將待簽名文檔解析,然後根據前面創建的私鑰和待簽名文檔的根元素創建 DOMSignContext
對象。
請注意,到這裏爲止都只是創建各種生成簽名所需數據的 XML 元素表示,並沒有開始真正地生成數字簽名。
6. 最後一步,生成簽名
sign 方法會生成簽名值,並作爲元素值創建 <SignatureValue>
元素,然後將整個 <Signature>
元素加入爲待簽名文檔根元素的直接子元素。
7. 輸出簽名後的文檔
數字簽名生成之後,使用 JAX P的 XML 轉換接口將簽名後的 XML 文檔輸出,查看簽名結果:
3.1.2 驗證簽名
本節介紹如何使用 Java SE 6提供的 XML 數字簽名 API 對上一節生成的數字簽名進行驗證。驗證代碼如表5。
表 5: 驗證 XML 數字簽名
1. 解析簽名後生成的 XML 文檔(行2 - 行6)
對於 Enveloped 格式的 XML 簽名而言,生成的 <Signature>
元素位於被簽名的 XML 中,所以這裏首先使用 JAXP 將簽名後生成的 XML 文檔解析。
2. 查找簽名元素(行8 - 行14)
所有與 XML 數字簽名相關的信息都存放在 <Signature>
元素中,所以需要先取到 <Signature>
元素。由於前面在生成簽名的時候將 <Signature>
元素存放爲文檔根元素的直接子元素,所以這裏根據元素名 ”Signature” 進行搜索,搜索到的第一個元素即爲 <Signature>
元素。
3. 構造 <Signature>
元素(行16 - 行19)
使用 XMLSignatureFactory
類的 unmarshalXMLSignature
方法從 DOM 節點構造出 XMLSignature
對象,爲下一步驗證簽名作準備。
4. 獲取驗證簽名所需的公共密鑰(行20 - 行22)
在本例中,驗證密鑰以 <DSAKeyValue>
的格式存放於 <KeyInfo>
元素中,這裏使用 XMLSignature
對象的接口取出公鑰。
5. 創建DOMValidateContext
(行24 - 行25)
在驗證簽名的過程中,需要創建 DOMValidateContext
對象來指定上下文信息,參數爲前面獲取到的驗證公鑰和 <Signature>
元素。
6. 驗證簽名(行27 - 行28)
驗證簽名所需的所有信息都已就緒,開始使用XMLSignature
對象提供的接口進行驗證。
7. 檢查驗證結果(行30 - 行46)
如果簽名驗證失敗,則分別對 <Reference>
元素的簽名值和其中的每一個引用進行驗證,進一步確定導致驗證失敗的原因。如果簽名值驗證成功,而某個引用驗證失敗,則說明是該引用新生成的摘要值與原文檔中的摘要值不匹配導致驗證失敗。
以簽名後生成的 XML 文檔作爲輸入,執行驗證程序,從程序的輸出信息可以判斷出驗證成功:
接下來將包含有 <Signature>
元素的待驗證文檔作一點改動,把 Peter 的支付信息中的金額改爲 60000 美元,再次執行程序進行驗證,則驗證失敗:
輸出信息提示簽名值驗證成功,而對整個文檔的引用驗證失敗。簽名值驗證成功是因爲 <SignedInfo>
沒有改動,對文檔的引用驗證失敗是因爲前面修改了文檔中的數據。
3.2 生成並驗證Enveloping格式的簽名
Enveloping 格式的簽名指 <Signature>
元素包含着被簽名的數據內容,如表 6 所示:
表 6 Enveloping 格式的數字簽名
在 Enveloping 格式的數字簽名中,待簽名的 XML 內容需要通過 URI 或者 Transform 進行引用。
3.2.1 生成簽名
爲了引用待簽名的內容,首先查找到 Simon 的支付記錄對應的 DOM 元素,使用 XMLStructure
對其進行包裝,然後生成 XMLObject
,併爲其指定 id
爲 "SimonPayment"
。隨後在創建 Referenc
的時候,同樣指定引用 id
爲 "SimonPayment"
。在創建 XMLSignature
對象的時候將待簽名的 XMLObject
作爲參數,這些 XMLObject
包含的 XML 內容將會成爲 <Signature>
元素的子元素,從而創建出 Enveloping 格式的簽名。程序的其他部分與生成 Enveloped 格式的數字簽名相同。
簽名之後生成的 <Signature>
元素如下:
表 5 中的驗證程序同樣可以用來驗證上面生成的 Enveloping 格式的 XML 簽名。
3.3 生成並驗證Detached格式的簽名
Detached 格式的簽名指 <Signature>
元素與被簽名的數據內容之間是彼此分離的,既不是包含關係也不是被包含關係,如下表所示:
Detached 格式多用於對外部獨立的數據對象進行簽名,使用 URI 來引用外部數據對象,也可以引用同一 XML 文檔中的其他元素,對之進行數字簽名。
表3中待簽名的 XML 文檔包含有兩次信用卡交易的支付信息,第一個是 Simon 的信用卡交易記錄,第二個是 Peter 的。本節將對 Peter的支付記錄進行數字簽名,所使用的簽名格式是 Detached 格式。爲了讓 <Signature>
元素能夠引用到包含有 Peter 信用卡交易信息的 XML 元素,這裏用 id
屬性對 Peter 的 <PaymentInfo>
元素加以引用。
3.3.1 生成簽名
本節的大部分代碼都與生成 Enveloped 格式簽名的代碼相同,只是在創建 Reference
的時候有些不同:
"#PeterPayment"
用於引用位於同一文檔中的 Peter 的 <PaymentInfo>
元素。
3.3.2 驗證簽名
使用表 5 中的 validate
方法對生成的數字簽名進行驗證,輸出信息表示驗證成功。然後在生成的 XML 文檔中,將 Simon 的支付金額改爲 30000,重新驗證,結果依然爲驗證成功。這是因爲只對 Peter 的支付記錄進行簽名,所以 Simon 的支付信息改變,不會影響數字簽名的驗證結果。如果再將 Peter 的支付金額改爲 50000,重新驗證,則輸出信息顯示驗證失敗。
4. 總結
同傳統意義的數字簽名技術相比,XML 數字簽名技術有很多不可替代的優點。它能夠在保持 XML 文檔良構性的前提下,對文檔內容進行細粒度的簽名和驗證;通過資源引用和轉換的機制,擴大了簽名的作用範圍,更能夠滿足分佈式應用系統中的安全需求。
W3C 組織制訂的 XML 數字簽名規範規定了標準的 XML 簽名語法和處理規則,而Java SE 6則爲 XML 數字簽名提供了標準的 Java 接口。目前這些 XML 數字簽名規範和程序標準還在進一步完善中,相信隨着技術的發展,XML 數字簽名技術必將得到越來越廣泛的應用。