如何:用非对称密钥对 XML 元素进行加密

更新:2007 年 11 月

可以使用 System.Security.Cryptography.Xml 命名空间中的类对 XML 文档中的元素进行加密。 XML 加密是交换或存储加密的 XML 数据的标准方法,无需担心数据被轻易读取。 有关 XML 加密标准的更多信息,请参见位于 http://www.w3.org/TR/xmldsig-core/ 上的关于 XML 加密的 WWW 联合会 (W3C) 规范。

可以使用 XML 加密,用包含加密的 XML 数据的 <EncryptedData> 元素替换任何 XML 元素或文档。 <EncryptedData> 元素可以包含一些子元素来收入关于加密期间使用的密钥和进程的信息。 XML 加密允许一个文档包含多个加密的元素,并允许多次加密一个元素。 此过程中的代码示例演示了如何创建一个 <EncryptedData> 元素和几个其他子元素,以便以后在解密过程中使用。

此示例使用了两个密钥对一个 XML 元素进行加密。 它生成 RSA 公钥/私钥对,并将密钥对保存到安全密钥容器中。 然后示例使用高级加密标准 (AES) 算法(又称为 Rijndael 算法)创建一个单独的会话密钥。 示例使用 AES 会话密钥对 XML 文档进行加密,然后使用 RSA 公钥对 AES 会话密钥进行加密。 最后,示例将加密的 AES 会话密钥和加密的 XML 数据保存到 XML 文档中的新 <EncryptedData> 元素中。

若要对 XML 元素进行解密,请从密钥容器中检索出 RSA 私钥,用它对会话密钥进行解密,然后使用会话密钥对文档进行解密。 有关如何对使用此过程加密的 XML 元素进行解密的更多信息,请参见 如何:用非对称密钥对 XML 元素进行解密

此示例适合于以下情形:多个应用程序需要共享加密数据,或者一个应用程序需要在多次运行之间保存加密数据。

用非对称密钥对 XML 元素进行加密

  1. 创建 CspParameters 对象,并指定密钥容器的名称。

    Dim cspParams As New CspParameters()
    cspParams.KeyContainerName = "XML_ENC_RSA_KEY"
    
         CspParameters cspParams = new CspParameters();
            cspParams.KeyContainerName = "XML_ENC_RSA_KEY";
    
  2. 使用 RSACryptoServiceProvider 类生成一个对称密钥。 当您将 CspParameters 对象传递给 RSACryptoServiceProvider 类的构造函数时,该密钥将自动保存到密钥容器中。 此密钥将被用来对 AES 会话密钥进行加密,并且以后可以检索此密钥来对 AES 会话密钥进行解密。

    Dim rsaKey As New RSACryptoServiceProvider(cspParams)
    
         RSACryptoServiceProvider rsaKey = new RSACryptoServiceProvider(cspParams);
    
  3. 通过从磁盘加载 XML 文件创建 XmlDocument 对象。 XmlDocument 对象包含要加密的 XML 元素。

    ' Create an XmlDocument object.
    Dim xmlDoc As New XmlDocument()
    
    ' Load an XML file into the XmlDocument object.
    Try
        xmlDoc.PreserveWhitespace = True
        xmlDoc.Load("test.xml")
    Catch e As Exception
        Console.WriteLine(e.Message)
    End Try
    
         // Create an XmlDocument object.
            XmlDocument xmlDoc = new XmlDocument();
    
            // Load an XML file into the XmlDocument object.
            try
            {
                xmlDoc.PreserveWhitespace = true;
                xmlDoc.Load("test.xml");
            }
            catch (Exception e)
            {
                Console.WriteLine(e.Message);
            }
    
  4. XmlDocument 对象中找到指定的元素,然后创建一个新的 XmlElement 对象来表示要加密的元素。 在此示例中,"creditcard" 元素要加密。

    Dim elementToEncrypt As XmlElement = Doc.GetElementsByTagName(ElementToEncryptName)(0)
    
    
    ' Throw an XmlException if the element was not found.
    If elementToEncrypt Is Nothing Then
        Throw New XmlException("The specified element was not found")
    End If
    
         XmlElement elementToEncrypt = Doc.GetElementsByTagName(ElementToEncrypt)[0] as XmlElement;
    
            // Throw an XmlException if the element was not found.
            if (elementToEncrypt == null)
            {
                throw new XmlException("The specified element was not found");
    
            }
    
  5. 使用 RijndaelManaged 类创建一个新的会话密钥。 此密钥将对 XML 元素进行加密,然后其自身将被加密并放入 XML 文档中。

    ' Create a 256 bit Rijndael key.
    sessionKey = New RijndaelManaged()
    sessionKey.KeySize = 256
    
             // Create a 256 bit Rijndael key.
                sessionKey = new RijndaelManaged();
                sessionKey.KeySize = 256;
    
  6. 创建 EncryptedXml 类的新实例,并使用它通过会话密钥对指定的元素进行加密。 EncryptData 方法将加密的元素作为加密字节的数组返回。

    Dim eXml As New EncryptedXml()
    
    Dim encryptedElement As Byte() = eXml.EncryptData(elementToEncrypt, sessionKey, False)
    
             EncryptedXml eXml = new EncryptedXml();
    
                byte[] encryptedElement = eXml.EncryptData(elementToEncrypt, sessionKey, false);
    
  7. 构造一个 EncryptedData 对象,然后用加密的 XML 元素的 URL 标识符填充它。 此 URL 标识符使解密方知道 XML 包含一个加密元素。 可以使用 XmlEncElementUrl 字段指定 URL 标识符。 纯文本 XML 元素将被 <EncrypotedData> 元素(该元素由 EncryptedData 对象封装)替换。

    Dim edElement As New EncryptedData()
    edElement.Type = EncryptedXml.XmlEncElementUrl
    edElement.Id = EncryptionElementID
    
             EncryptedData edElement = new EncryptedData();
                edElement.Type = EncryptedXml.XmlEncElementUrl;
                edElement.Id = EncryptionElementID;
    
  8. 创建 EncryptionMethod 对象,该对象被初始化为用来生成会话密钥的加密算法的 URL 标识符。 将 EncryptionMethod 对象传递给 EncryptionMethod 属性。

    edElement.EncryptionMethod = New EncryptionMethod(EncryptedXml.XmlEncAES256Url)
    
             edElement.EncryptionMethod = new EncryptionMethod(EncryptedXml.XmlEncAES256Url);
    
  9. 创建 EncryptedKey 对象,以包含加密的会话密钥。 对会话密钥进行加密,将它添加到 EncryptedKey 对象中,然后输入会话密钥名称和密钥标识符 URL。

    Dim ek As New EncryptedKey()
    
    Dim encryptedKey As Byte() = EncryptedXml.EncryptKey(sessionKey.Key, Alg, False)
    
    ek.CipherData = New CipherData(encryptedKey)
    
    ek.EncryptionMethod = New EncryptionMethod(EncryptedXml.XmlEncRSA15Url)
    
             EncryptedKey ek = new EncryptedKey();
    
                byte[] encryptedKey = EncryptedXml.EncryptKey(sessionKey.Key, Alg, false);
    
                ek.CipherData = new CipherData(encryptedKey);
    
                ek.EncryptionMethod = new EncryptionMethod(EncryptedXml.XmlEncRSA15Url);
    
  10. 创建一个新的 DataReference 对象,它将加密的数据映射到一个特定会话密钥。 这是一个可选步骤,使用此步骤可以轻松指定 XML 文档的多个部分由一个密钥加密。

    Dim dRef As New DataReference()
    
    ' Specify the EncryptedData URI. 
    dRef.Uri = "#" + EncryptionElementID
    
    ' Add the DataReference to the EncryptedKey.
    ek.AddReference(dRef)
    
             DataReference dRef = new DataReference();
    
                // Specify the EncryptedData URI.
                dRef.Uri = "#" + EncryptionElementID;
    
                // Add the DataReference to the EncryptedKey.
                ek.AddReference(dRef);
    
  11. 将加密的密钥添加到 EncryptedData 对象中。

    edElement.KeyInfo.AddClause(New KeyInfoEncryptedKey(ek))
    
             edElement.KeyInfo.AddClause(new KeyInfoEncryptedKey(ek));
    
  12. 创建一个新的 KeyInfo 对象,以指定 RSA 密钥的名称。 将它添加到 EncryptedData 对象中。这将帮助解密方识别在对会话密钥进行解密时需要使用的正确的非对称密钥。

    ' Create a new KeyInfo element.
    edElement.KeyInfo = New KeyInfo()
    
    ' Create a new KeyInfoName element.
    Dim kin As New KeyInfoName()
    
    ' Specify a name for the key.
    kin.Value = KeyName
    
    ' Add the KeyInfoName element to the 
    ' EncryptedKey object.
    ek.KeyInfo.AddClause(kin)
    
             // Create a new KeyInfo element.
                edElement.KeyInfo = new KeyInfo();
    
                // Create a new KeyInfoName element.
                KeyInfoName kin = new KeyInfoName();
    
                // Specify a name for the key.
                kin.Value = KeyName;
    
                // Add the KeyInfoName element to the
                // EncryptedKey object.
                ek.KeyInfo.AddClause(kin);
    
  13. 将加密的元素数据添加到 EncryptedData 对象中。

    edElement.CipherData.CipherValue = encryptedElement
    
             edElement.CipherData.CipherValue = encryptedElement;
    
  14. EncryptedData 元素替换原始 XmlDocument 对象中的元素。

    EncryptedXml.ReplaceElement(elementToEncrypt, edElement, False)
    
             EncryptedXml.ReplaceElement(elementToEncrypt, edElement, false);
    
  15. 保存 XmlDocument 对象。

    xmlDoc.Save("test.xml")
    
             xmlDoc.Save("test.xml");
    

示例

Imports System
Imports System.Xml
Imports System.Security.Cryptography
Imports System.Security.Cryptography.Xml



Module Program

    Sub Main(ByVal args() As String)
        ' Create an XmlDocument object.
        Dim xmlDoc As New XmlDocument()

        ' Load an XML file into the XmlDocument object.
        Try
            xmlDoc.PreserveWhitespace = True
            xmlDoc.Load("test.xml")
        Catch e As Exception
            Console.WriteLine(e.Message)
        End Try
        ' Create a new CspParameters object to specify
        ' a key container.
        Dim cspParams As New CspParameters()
        cspParams.KeyContainerName = "XML_ENC_RSA_KEY"
        ' Create a new RSA key and save it in the container.  This key will encrypt 
        ' a symmetric key, which will then be encryped in the XML document.
        Dim rsaKey As New RSACryptoServiceProvider(cspParams)
        Try
            ' Encrypt the "creditcard" element.
            Encrypt(xmlDoc, "creditcard", "EncryptedElement1", rsaKey, "rsaKey")


            ' Save the XML document.
            xmlDoc.Save("test.xml")
            ' Display the encrypted XML to the console.
            Console.WriteLine("Encrypted XML:")
            Console.WriteLine()
            Console.WriteLine(xmlDoc.OuterXml)

        Catch e As Exception
            Console.WriteLine(e.Message)
        Finally
            ' Clear the RSA key.
            rsaKey.Clear()
        End Try


        Console.ReadLine()

    End Sub


    Sub Encrypt(ByVal Doc As XmlDocument, ByVal ElementToEncryptName As String, ByVal EncryptionElementID As String, ByVal Alg As RSA, ByVal KeyName As String)
        ' Check the arguments.  
        If Doc Is Nothing Then
            Throw New ArgumentNullException("Doc")
        End If
        If ElementToEncryptName Is Nothing Then
            Throw New ArgumentNullException("ElementToEncrypt")
        End If
        If EncryptionElementID Is Nothing Then
            Throw New ArgumentNullException("EncryptionElementID")
        End If
        If Alg Is Nothing Then
            Throw New ArgumentNullException("Alg")
        End If
        If KeyName Is Nothing Then
            Throw New ArgumentNullException("KeyName")
        End If
        ''''''''''''''''''''''''''''''''''''''''''''''''''
        ' Find the specified element in the XmlDocument
        ' object and create a new XmlElemnt object.
        ''''''''''''''''''''''''''''''''''''''''''''''''''
        Dim elementToEncrypt As XmlElement = Doc.GetElementsByTagName(ElementToEncryptName)(0)


        ' Throw an XmlException if the element was not found.
        If elementToEncrypt Is Nothing Then
            Throw New XmlException("The specified element was not found")
        End If
        Dim sessionKey As RijndaelManaged = Nothing

        Try
            ''''''''''''''''''''''''''''''''''''''''''''''''''
            ' Create a new instance of the EncryptedXml class 
            ' and use it to encrypt the XmlElement with the 
            ' a new random symmetric key.
            ''''''''''''''''''''''''''''''''''''''''''''''''''
            ' Create a 256 bit Rijndael key.
            sessionKey = New RijndaelManaged()
            sessionKey.KeySize = 256
            Dim eXml As New EncryptedXml()

            Dim encryptedElement As Byte() = eXml.EncryptData(elementToEncrypt, sessionKey, False)
            ''''''''''''''''''''''''''''''''''''''''''''''''''
            ' Construct an EncryptedData object and populate
            ' it with the desired encryption information.
            ''''''''''''''''''''''''''''''''''''''''''''''''''
            Dim edElement As New EncryptedData()
            edElement.Type = EncryptedXml.XmlEncElementUrl
            edElement.Id = EncryptionElementID
            ' Create an EncryptionMethod element so that the 
            ' receiver knows which algorithm to use for decryption.
            edElement.EncryptionMethod = New EncryptionMethod(EncryptedXml.XmlEncAES256Url)
            ' Encrypt the session key and add it to an EncryptedKey element.
            Dim ek As New EncryptedKey()

            Dim encryptedKey As Byte() = EncryptedXml.EncryptKey(sessionKey.Key, Alg, False)

            ek.CipherData = New CipherData(encryptedKey)

            ek.EncryptionMethod = New EncryptionMethod(EncryptedXml.XmlEncRSA15Url)
            ' Create a new DataReference element
            ' for the KeyInfo element.  This optional
            ' element specifies which EncryptedData 
            ' uses this key.  An XML document can have
            ' multiple EncryptedData elements that use
            ' different keys.
            Dim dRef As New DataReference()

            ' Specify the EncryptedData URI. 
            dRef.Uri = "#" + EncryptionElementID

            ' Add the DataReference to the EncryptedKey.
            ek.AddReference(dRef)
            ' Add the encrypted key to the 
            ' EncryptedData object.
            edElement.KeyInfo.AddClause(New KeyInfoEncryptedKey(ek))
            ' Set the KeyInfo element to specify the
            ' name of the RSA key.
            ' Create a new KeyInfo element.
            edElement.KeyInfo = New KeyInfo()

            ' Create a new KeyInfoName element.
            Dim kin As New KeyInfoName()

            ' Specify a name for the key.
            kin.Value = KeyName

            ' Add the KeyInfoName element to the 
            ' EncryptedKey object.
            ek.KeyInfo.AddClause(kin)
            ' Add the encrypted element data to the 
            ' EncryptedData object.
            edElement.CipherData.CipherValue = encryptedElement
            ''''''''''''''''''''''''''''''''''''''''''''''''''
            ' Replace the element from the original XmlDocument
            ' object with the EncryptedData element.
            ''''''''''''''''''''''''''''''''''''''''''''''''''
            EncryptedXml.ReplaceElement(elementToEncrypt, edElement, False)
        Catch e As Exception
            ' re-throw the exception.
            Throw e
        Finally
            If Not (sessionKey Is Nothing) Then
                sessionKey.Clear()
            End If
        End Try

    End Sub
End Module
using System;
using System.Xml;
using System.Security.Cryptography;
using System.Security.Cryptography.Xml;

class Program
{
    static void Main(string[] args)
    {
        // Create an XmlDocument object.
        XmlDocument xmlDoc = new XmlDocument();

        // Load an XML file into the XmlDocument object.
        try
        {
            xmlDoc.PreserveWhitespace = true;
            xmlDoc.Load("test.xml");
        }
        catch (Exception e)
        {
            Console.WriteLine(e.Message);
        }

        // Create a new CspParameters object to specify
        // a key container.
        CspParameters cspParams = new CspParameters();
        cspParams.KeyContainerName = "XML_ENC_RSA_KEY";

        // Create a new RSA key and save it in the container.  This key will encrypt
        // a symmetric key, which will then be encryped in the XML document.
        RSACryptoServiceProvider rsaKey = new RSACryptoServiceProvider(cspParams);

        try
        {
            // Encrypt the "creditcard" element.
            Encrypt(xmlDoc, "creditcard", "EncryptedElement1", rsaKey, "rsaKey");


            // Save the XML document.
            xmlDoc.Save("test.xml");

            // Display the encrypted XML to the console.
            Console.WriteLine("Encrypted XML:");
            Console.WriteLine();
            Console.WriteLine(xmlDoc.OuterXml);

        }
        catch (Exception e)
        {
            Console.WriteLine(e.Message);
        }
        finally
        {
            // Clear the RSA key.
            rsaKey.Clear();
        }


        Console.ReadLine();
    }

    public static void Encrypt(XmlDocument Doc, string ElementToEncrypt, string EncryptionElementID, RSA Alg, string KeyName)
    {
        // Check the arguments.
        if (Doc == null)
            throw new ArgumentNullException("Doc");
        if (ElementToEncrypt == null)
            throw new ArgumentNullException("ElementToEncrypt");
        if (EncryptionElementID == null)
            throw new ArgumentNullException("EncryptionElementID");
        if (Alg == null)
            throw new ArgumentNullException("Alg");
        if (KeyName == null)
            throw new ArgumentNullException("KeyName");

        ////////////////////////////////////////////////
        // Find the specified element in the XmlDocument
        // object and create a new XmlElemnt object.
        ////////////////////////////////////////////////
        XmlElement elementToEncrypt = Doc.GetElementsByTagName(ElementToEncrypt)[0] as XmlElement;

        // Throw an XmlException if the element was not found.
        if (elementToEncrypt == null)
        {
            throw new XmlException("The specified element was not found");

        }
        RijndaelManaged sessionKey = null;

        try
        {
            //////////////////////////////////////////////////
            // Create a new instance of the EncryptedXml class
            // and use it to encrypt the XmlElement with the
            // a new random symmetric key.
            //////////////////////////////////////////////////

            // Create a 256 bit Rijndael key.
            sessionKey = new RijndaelManaged();
            sessionKey.KeySize = 256;

            EncryptedXml eXml = new EncryptedXml();

            byte[] encryptedElement = eXml.EncryptData(elementToEncrypt, sessionKey, false);
            ////////////////////////////////////////////////
            // Construct an EncryptedData object and populate
            // it with the desired encryption information.
            ////////////////////////////////////////////////

            EncryptedData edElement = new EncryptedData();
            edElement.Type = EncryptedXml.XmlEncElementUrl;
            edElement.Id = EncryptionElementID;
            // Create an EncryptionMethod element so that the
            // receiver knows which algorithm to use for decryption.

            edElement.EncryptionMethod = new EncryptionMethod(EncryptedXml.XmlEncAES256Url);
            // Encrypt the session key and add it to an EncryptedKey element.
            EncryptedKey ek = new EncryptedKey();

            byte[] encryptedKey = EncryptedXml.EncryptKey(sessionKey.Key, Alg, false);

            ek.CipherData = new CipherData(encryptedKey);

            ek.EncryptionMethod = new EncryptionMethod(EncryptedXml.XmlEncRSA15Url);

            // Create a new DataReference element
            // for the KeyInfo element.  This optional
            // element specifies which EncryptedData
            // uses this key.  An XML document can have
            // multiple EncryptedData elements that use
            // different keys.
            DataReference dRef = new DataReference();

            // Specify the EncryptedData URI.
            dRef.Uri = "#" + EncryptionElementID;

            // Add the DataReference to the EncryptedKey.
            ek.AddReference(dRef);
            // Add the encrypted key to the
            // EncryptedData object.

            edElement.KeyInfo.AddClause(new KeyInfoEncryptedKey(ek));
            // Set the KeyInfo element to specify the
            // name of the RSA key.

            // Create a new KeyInfo element.
            edElement.KeyInfo = new KeyInfo();

            // Create a new KeyInfoName element.
            KeyInfoName kin = new KeyInfoName();

            // Specify a name for the key.
            kin.Value = KeyName;

            // Add the KeyInfoName element to the
            // EncryptedKey object.
            ek.KeyInfo.AddClause(kin);
            // Add the encrypted element data to the
            // EncryptedData object.
            edElement.CipherData.CipherValue = encryptedElement;
            ////////////////////////////////////////////////////
            // Replace the element from the original XmlDocument
            // object with the EncryptedData element.
            ////////////////////////////////////////////////////
            EncryptedXml.ReplaceElement(elementToEncrypt, edElement, false);
        }
        catch(Exception e)
        {
            // re-throw the exception.
            throw e;
        }
        finally
        {
            if (sessionKey != null)
            {
                sessionKey.Clear();
            }

        }

    }

}

此示例假定一个名为 "test.xml" 的文件与编译的程序在同一目录中。 它还假定 "test.xml" 包含一个 "creditcard" 元素。 您可以将以下 XML 放入名为 test.xml 的文件中,并将它和此示例一起使用。

<root>
    <creditcard>
        <number>19834209</number>
        <expiry>02/02/2002</expiry>
    </creditcard>
</root>

编译代码

安全性

不要以纯文本格式存储对称加密密钥,也不要用纯文本在计算机之间传输对称密钥。 另外,不要用纯文本存储或传输非对称密钥对的私钥。 有关对称和非对称加密密钥的更多信息,请参见 生成加密和解密的密钥

不要将密钥直接嵌入到源代码中。 可以通过使用 MSIL 反汇编程序 (Ildasm.exe),或通过在诸如记事本的文本编辑器中打开程序集,从程序集轻松读取嵌入的密钥。

加密密钥使用完以后,请通过将每个字节设置为零,或通过调用托管密码类的 Clear 方法,将它从内存中清除。 加密密钥有时可以被调试器从内存读取或从硬盘读取(如果内存位置被分页到磁盘)。

请参见

任务

如何:用非对称密钥对 XML 元素进行解密

参考

System.Security.Cryptography.Xml

其他资源

XML 加密和数字签名