許多新興的技術,比如 Web 服務,都將 XML 廣泛應用於數據交換。因此,XML 在傳輸和存儲時的安全性成為非常重要的問題。本次系列文章介紹了保護 XML 的各種技術。第 1 部分介紹了 XML 安全性方面的基本支持技術。本文在前一部分的基礎上,介紹了保障 XML 安全性的核心技術——XML 加密和 XML 簽名。本文還循序漸進地介紹了用這些技術保護 XML 消息的過程。
有許多現有的加密技術,比如安全套接字層(Secure Sockets Layer,SSL),可以對 XML 文檔進行加密,這和其他任何文檔都一樣,那麼為什麼還需要另一種加密標准呢?在本文中,我將從考察其目標和動機開始,引出另一種加密標准—— XML 加密(XML Encryption)。
XML 加密
XML 加密的首要目標是:
支持對任意數字內容的加密,包括 XML 文檔。
確保經過加密的數據不管是在傳輸過程中還是在存儲時,都不能被未經授權的人員訪問到。
即便在消息傳送中的每一跳,都能維持數據的安全性——這句話的意思是,不僅數據正在傳輸的過程中要保證安全性(這就是 SSL 所作出的保證),當數據在某個特定的節點停留的時候,也要保證其安全性。
以 XML 形式表現被加密的數據。
可以從 XML 文檔中選出一部分內容進行加密。
我們拿這個目標與基於 HTTP 的 SSL(又稱 HTTPS)必須提供的功能進行比較。如果使用基於 HTTP 的 SSL,整條消息都會被加密;在第一個目的地,整條消息又被解密,在它被重新全部加密傳輸到第二跳的節點之前,可能受到嗅探器的威脅。基於 HTTP 的 SSL 提供的加密僅僅在傳輸的過程中存在,它是不持久的。
XML 加密概覽
設計目標 之一清晰地表明,經過加密的 XML 數據應該用 XML 的形式表現。在得到的 XML 中,有兩個重要的元素值得理解清楚: <EncryptedData> 和 <EncryptedKey> 。 <EncryptedData> 中包含除加密密鑰之外所有經過加密的內容。當對加密密鑰進行加密的時候,得到的結果就放置在 <EncryptedKey> 元素中。
除了加密過的內容以外,XML加密還允許您在上面討論的兩個元素中指定用於加密的算法,或者是加入用於加密的密鑰。這意味著您即便不將它們分別保存,也可以在後續引用這些數據;或者通過其他傳輸機制發送給接收方。
注意:XML 加密沒有定義任何新的加密算法,只使用已有的算法。
XML 加密過程
在本節中,我先向您展示一段普通的 XML 代碼,然後介紹用 XML Encryption 標准(參閱 參考資料)對這段代碼進行加密的各個步驟。我將從清單 1 中顯示的 XML 開始。
清單 1. XML 示例
<?XML version="1.0"?>
<PurchaSEOrderRequest>
<Order>
<Item>
<Code>Screw001</Code>
<Description>Screw with half centimeter thread</Description>
</Item>
<Quantity>2</Quantity>
</Order>
<Payment>
<CreditCard>
<Type>MasterCard</Type>
<Number>1234567891234567</Number>
<ExpiryDate>20050501</ExpiryDate>
</CreditCard>
<PurchaseAmount>
<Amount>30000</Amount>
<Currency>INR</Currency>
<Exponent>-3</Exponent>
</PurchaseAmount>
</Payment>
</PurchaSEOrderRequest>
清單 2 示范了如何對 清單 1中的部分 XML 進行加密。在這個清單之後,我解釋了加密過程中的每一個步驟。
清單 2. XML 加密
//Get the DOM document object for the XML that you
// want to encrypt.
// getDocument method that takes XML file name as input
// and returns DOM document provided in Listing 3 (Step 1)
Document doc = XmlUtil.getDocument(XMLFileName);
String xpath = "/PurchaSEOrderRequest/Payment";
// Step 2. Get the shared secret. This key is used to encrypt the
// XML content
Key dataEncryptionKey = getKey();
// Algorithm type used to generate shared secret
// i.e. content encryption key
AlgorithmType dataEncryptionAlgoType = AlgorithmType.TRIPLEDES;
// Get the key pair. You are interested in the public key
// as that is the one you will use for encrypting the
// XML content
KeyPair keyPair = getKeyPair();
// Step 3. Get the public key of the key pair
Key keyEncryptionKey = keyPair.getPublic();
// Algorithm type used to generate the public
// private key pair
AlgorithmType keyEncryptionAlgoType = AlgorithmType.RSA1_5;
KeyInfo keyInfo = new KeyInfo();
// Step 4
try {
Encryptor enc =
new Encryptor(
doc,
dataEncryptionKey,
dataEncryptionAlgoType,
keyEncryptionKey,
keyEncryptionAlgoType,
keyInfo);
XPath xpath = new XPath(xPath);
// Step 5
try {
enc.encryptInPlace(xpath);
} catch (XPathException e1) {
System.out.println("XPAth is not correct");
e1.printStackTrace();
}
XmlUtil.XMLOnStdOut(doc);
} catch (Exception e) {
System.out.println("Some exception");
e.printStackTrace();
}
步驟 1:將 XML 轉換成 DOM 對象,如清單 3 所示:
清單 3. 根據 XML 創建 DOM 對象
public static Document getDocument(String fileName) {
Document doc = null;
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
File f = new File(fileName);
DocumentBuilder builder = null;
try {
builder = factory.newDocumentBuilder();
} catch (ParserConfigurationException e) {
System.out.println("Parse configuration exception");
e.printStackTrace();
}
try {
doc = builder.parse(f);
} catch (Exception e1) {
System.out.println("Some exception");
e1.printStackTrace();
}
return doc;
}
步驟 2:獲得共享密鑰(shared secret)。您要用這個密鑰來加密 XML 內容。本文附帶的源代碼使用的 XML 加密方法只能識別三重 DES(Triple-DES)加密算法,因此我就用這種算法創建密鑰。
步驟 3:參照本系列文章 第 1 部分所述,獲得公-私密鑰對中的公鑰;您需要用這個公鑰給共享密鑰加密。您從第 1 部分中可以看到,這個公鑰是基於 RSA 算法生成的。
步驟 4:利用一個數據加密密鑰、一個密鑰加密密鑰、與這兩個密鑰相關聯的算法、以及將來包含在輸出信息中的密鑰信息,根據它們來創建一個 Encryptor 對象。創建 Encryptor 對象時指定的算法必須與密鑰相符。 Encryptor 是加密過程中的主要對象。它的類在 com.verisign.xmlenc 這個包中。 Encryptor 根據 W3C XML Encryption 規范進行加密。您可以指定想要使用哪種加密類型,是 Element 還是 Content。在 清單 2 中,加密類型是 Element,這也是默認的類型。 Encryptor 要理解 XPath 表達式,這樣才能識別出需要加密的 XML 元素。
步驟 5:最後一步,調用 Encryptor 對象的 encrypt 或者 encryptInPlace 方法,並將 XPath 作為輸入參數傳入。XPath 定義了 XML 內部需要進行加密的元素。這個元素的所有子元素,以及 XPath 所指向的屬性也都要進行加密。在本例中,您加密的是 XML 中的 /PurchaSEOrderRequest/Payment 元素。 encrypt 和 encryptInPlace 兩個方法都用傳入的共享密鑰對 XPath 指定的 XML 元素進行加密,兩種方法也都用公鑰對共享密鑰進行加密,並將加密結果嵌入到 XML 加密後的內容之中。這兩種方法的唯一區別在於, encrypt 返回一個全新的 DOM 文檔,其中包含加密後的數據,而 encryptInPlace 方法對原有的文檔本身進行修改,使其中包含加密後的數據。加密過的 XML 如清單 4 所示。
清單 4. 加密後的 XML
<?XML version="1.0" encoding="UTF-8"?>
<PurchaSEOrderRequest>
<Order>
<Item>
<Code>Screw001</Code>
<Description>Screw with half centimeter thread</Description>
</Item>
<Quantity>2</Quantity>
</Order>
<xenc:EncryptedData Type="http://www.w3.org/2001/04/XMLenc#Element"
xmlns:xenc="http://www.w3.org/2001/04/XMLenc#">
<xenc:EncryptionMethod Algorithm=
"http://www.w3.org/2001/04/XMLenc#tripledes-cbc"/>
<ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/XMLdsig#">
<xenc:EncryptedKey>
<xenc:EncryptionMethod Algorithm=
"http://www.w3.org/2001/04/XMLenc#rsa-1_5"/>
<xenc:CipherData>
<xenc:CipherValue>
F1aIpdp3axm8nFofx/xX62VlsxildddHcxaevd7sbr+lv/fzZ7e8ovmKGQopAjclxPTybpkW
YG8GVcOIbD4UGR24CNxeB7eZCws5/RKBTqKp+76FkVxf+G+EqgMmueRqoaF4oYOrTKquWLnR
kiSOFmplRaJ8G7bR2j0eTFdiFRk=
</xenc:CipherValue>
</xenc:CipherData>
</xenc:EncryptedKey>
</ds:KeyInfo>
<xenc:CipherData>
<xenc:CipherValue>
KMkufRUY7rs0i+4jX6VhviiUIbYWay1KbwhTQxH9SaqJ6HA+Qc2Ce7TVZUQuH0GGD4xTR8hB
hOls+hgHA16EfmmxLd3E+YqO4sXQ+GkX9O9EcO4ULha/q1KmP2yNGNy/tavdj9a7JuZnnNGV
/M4gxdt5fCJXT0A9bw9HwKR/Pc81rZYWa7fOrmvDvC7Q+//OCzkqcAaCmAHEySWbv2vK3T+a
GlQOI2Wooxa9hm7Dx70BuLI8ihhSAV3moK+JAPdn1vdCpoFKdzzq2HSh/yOisYZvQOh+jIks
MW8oUzWnVUe/DFztPtvvDKbPE/xoAasixlbDLa42gFFe9uzEeIG89XBMSkZtTio0zn9xPPSf
Dc0WFMy+UoLnCA==
</xenc:CipherValue>
</xenc:CipherData>
</xenc:EncryptedData>
</PurchaSEOrderRequest>
清單 4是部分加密的 XML 代碼片斷。只有當接收者具有與加密數據時使用的公鑰相對應的私鑰時,才能閱讀這部分被加密的數據。
最後說一下,清單 5 中的代碼可以對加密過的 XML 進行解密。
清單 5. XML 解密
// Get the DOM document object for the XML that you
// want to decrypt (The one shown in Listing 4)
// The getDocument method that takes an XML file name as input
// and returns a DOM document is provided in Listing 3 (Step 1)
Document doc = XmlUtil.getDocument(encryptedXMLFileName);
// Step 2. Get the private key of the pair whose public key was
// used to encrypt the XML
Key privateKey = keyPair.getPrivate();
// Specifying the XPath at which encrypted data is lying
// in the XML
// Step 3. Specify XPath expression
String xpath = "//xenc:EncryptedData";
// Specify the namespace that relates to the XPath
// expression
String[] ns =
{ "xenc", "http://www.w3.org/2001/04/XMLenc#" };
// Create the XPath helper with the XPath expression and a map
// of namespaces that relate to the XPath expression
XPath xpath = new XPath(xPath, ns);
// Step 4. Create the Decryptor object with decryption key and
// location of the encrypted data to be decrypted
Decryptor decrypt = null;
try {
decrypt = new Decryptor(doc, privateKey, xpath);
} catch (Exception e) {
System.out.println("Some exception");
e.printStackTrace();
}
// Step 5. Method decryptInPlace is called to decrypt the
// encrypted contents of the XML
try {
decrypt.decryptInPlace();
} catch (Exception e1) {
System.out.println("Some exception");
e1.printStackTrace();
}
清單 5示范了當您有正確的私鑰時,如何對加密的數據進行解密。下面的步驟解釋了解密的過程。
步驟 1:將加密過的 XML 轉換成 DOM 對象,這一步與加密過程相同。
步驟 2:根據用於加密 XML 的公鑰,獲取密鑰對中對應的私鑰。請注意,解密過程使用的是加密 XML 時使用的公鑰所對應的私鑰。.
步驟 3:創建 XPath 以及相關名稱空間,用於表示加密過的數據在加密過的 XML 中的位置。在本例中,XPath 的值是 //xenc:EncryptedData 。加密過的數據總是在加密過的 XML 中的 xenc:EncryptedData 元素下面,而與哪個元素被加密無關。XPath 為 //xenc:EncryptedData 則表示,從 XML 中可能出現加密數據的任何地方查找 EncryptedData 元素。
步驟 4:用解密密鑰和需要解密的加密數據所在的位置創建 Decryptor 對象。 Decryptor 是解密過程中的主要對象。它的類在 com.verisign.xmlenc 包中。 Decryptor 根據 W3C XML Encryption 規范進行解密(參閱 參考資料)。解密過程支持 Element 和 Content 兩種類型。為了識別需要解密的 XML 元素, Decryptor 要能理解 XPath 表達式。
步驟 5:解密過程的最後一個步驟是在 Decryptor 對象中調用 decryptInPlace 或者 decrypt 方法。這兩種方法調用都使用提供的私鑰來解密共享密鑰(共享密鑰是已加密消息中的一部分),然後用這個共享密鑰來解密消息的其余部分。兩種調用之間的唯一區別在於, decrypt 對 XML 解密之後創建一個新的 DOM 對象,而 decryptInPlace 在作為輸入接收的同一 DOM 對象中解密消息。
XML 簽名
數字簽名已經應用了相當長一段時間,任何數字化內容都可以用公鑰密碼標准(Public Key Cryptography Standards,最常用的是 PKCS#7 簽名)進行數字簽名。安全多用途 Internet 郵件擴展(Secure Multipurpose Internet Mail Extensions,S/MIME)允許將數字簽名附加到電子郵件消息上,這樣接收方就可以驗證簽名者的身份。
XML 簽名是對現有數字簽名基礎設施的擴展。下面列出了創建 XML 簽名的一些目標和動機:
在數字簽名周圍建立一些結構,這樣就可以用 XML 文檔的形式來表現數字簽名。
實現對一部分 XML 進行簽名,而剩余部分則不簽名。
實現對同一份 XML 文檔的不同部分使用多於一種的數字簽名。
不僅僅在文檔傳送和通信的時候使用簽名,還要使簽名能夠持久保留。
XML 簽名概覽
XML 簽名可以用於對包括 XML 文檔在內的任何數字內容進行簽名。對數字內容進行簽名的過程分為兩個階段。在第一階段中,對數字內容進行整理,得到的結果放在一個 XML 元素中。第二階段挑選出經過整理的值,並對其進行簽名。這樣做的原因非常簡單:對原始內容進行整理之後,可以得到一個很小的但是唯一的加密結果(稱為摘要),這樣比對原始內容進行簽名花費的時間少。
當 XML(或其中的一部分)經過數字簽名之後,得到的 XML 簽名用一個 XML 元素表現出來,這個元素的標識是 <Signature> ,最初的內容與這個數字簽名的關系基於下面幾種 XML 簽名類型:
封外簽名(Enveloping signature): <Signature> 元素中包含了進行數字簽名的元素。被簽名的元素成為了 <Signature> 元素的子元素。
封內簽名(Enveloped signature): <Signature> 元素成為被簽名數據的子元素。 <Signature> 元素通過它其中的 <Reference> 元素提供的信息引用被簽名的元素。
分離簽名(Detached signature ): <Signature> 元素與被簽名的元素各自獨立存在。被簽名的元素和 <Signature> 元素可以同屬於一個文檔,或者, <Signature> 元素也可以在另一個完全不同的文檔中。
除了引用被簽名的數字內容之外, <Signature> 元素還包括了有關如下方面的信息:
用於使數字內容規范化的方法。
為待簽名的規范化元素生成簽名的算法。
指定在整理之前如何處理待簽名元素的附加信息。
XML 簽名過程
接著 清單 1中的例子看,我現在將帶你經歷對 XML 元素進行數字簽名的全過程。清單 6 示范了如何對部分 XML 進行數字簽名;簽名過程中的每一步驟都會在這個清單後面解釋。
清單 6. 數字簽名的過程
// Get the DOM document object for the XML that you
// want to digitally sign.
// getDocument method that takes XML file name as input
// and returns DOM document is provided in Listing 3 (Step 1)
Document doc = XmlUtil.getDocument(XMLFileName);
// XPath expression that is to be digitally signed
String xpath = "/PurchaSEOrderRequest/Payment";
// Step 2. Get both the public and private keys. The private key is
// used to digitally sign the content, whereas the public key
// is sent along with the digitally signed content for the
// receiver to verify the digital signature.
contentKeyPair keyPair = getKeyPair();
PublicKey verificationKey = keyPair.getPublic();
PrivateKey privateKey = keyPair.getPrivate();
// Step 3. Create a signer object passing the document whose part
// is to be signed, the private key to be used for
// signing, and the public key that is to be used
// for verification.
Signer signer = null;
// Step 4
try {
signer = new Signer(doc, privateKey,
verificationKey);
XPath location1 =
new XPath("/PurchaSEOrderRequest/Payment/CreditCard");
signer.addReference(location1);
XPath location2 =
new XPath("/PurchaSEOrderRequest/Payment/PurchaseAmount");
signer.addReference(location2);
} catch (Exception e) {
e.printStackTrace();
}
// Step 5. Use the signer object to sign the document passing the
// XPath of the element that is to be signed.
try {
d = signer.sign(new XPath(xPath));
} catch (Exception e1) {
e1.printStackTrace();
}
步驟 1:同前面加密的步驟一樣,將需進行數字簽名的 XML 轉換為 DOM 對象。
步驟 2:獲得您在本系列第 1 部分的“ 生成公-私密鑰對”一節中生成的密鑰對,其中同時包括公鑰和私鑰。您將用私鑰對內容進行數字簽名,然後將公鑰隨著經過數字簽名的消息一起發送到接收方,以便用來驗證其中的數字簽名。前面講過,公-私密鑰對是基於 RSA 算法的。
步驟 3:根據需要簽名的 DOM 文檔、用來簽名的私鑰以及用於驗證的公鑰,創建一個 signer 對象。驗證用的公鑰並不是一定要有。 signer 是數字簽名過程中的主要對象。它的類在 com.verisign.xmlsig 包中。 signer 對象根據 W3C XML Signature 規范(參閱 參考資料)對 XML 文檔簽名。共支持全部三種簽名模式:封外、封內以及分離模式。
步驟 4:指定 XML 中的哪一部分需要簽名。通過增加到 Signer 對象的引用,可以指定數字簽名的 XML 位置。增加引用的方法是調用 Signer 對象中的 addReference 方法。需要簽名的元素的 XPath 也作為一個參數提供給 addReference 。您可以多次調用 addReference ,來指定想要進行簽名的不同元素。
步驟 5:最後一步是對 XML 簽名,您可以通過調用 signer 對象中的 sign 方法來完成。當調用 sign 方法時,如果不傳遞任何參數的話,生成的就是封外簽名。調用帶有 XPath 參數的 sign 方法,並且數字簽名放置在文檔中,則生成封內簽名。
結束語
隨著這個 XML 安全專題深入到這裡,我已經定義了安全的含義,並討論了 XML 規范化、PKI 基礎設施、XML 加密和 XML 簽名。通過介紹 XML 安全背後的理論,並竭力向您展示 XML 加密和數字簽名有多麼容易,希望這一切努力能為您揭開 XML 安全的神秘面紗。
在今後的文章中,我將要討論安全性斷言標記語言(Security Assertion Markup Language,SAML)和 XML 密鑰管理系統( XML Key Management System,XKMS)。SAML 可以用於在不同的協作實體之間傳送憑證以及其他一些與主題、人等相關的信息,而不會損失信息的所屬關系。通過將管理公鑰基礎設施(Public Key Infrastructure,PKI)的復雜性從客戶機應用程序提取到一個可信任的第三方機構中, XKMS 使得管理 PKI 變得非常容易。