开发者

Problem with decrypting xml document

I have written some code that has encrypted an XML config file containing user credentials, and also the code to decrypt that file. When I run the encryption and decryption together on my local machine it works as expected. However, when I deploy the program, with only the decrypt code, the xml file will not decrypt. I get a cryptographic Exception: Bad Data? Here is my code:

    public static void Encrypt(XmlDocument Doc, string ElementToEncrypt, string EncryptionElementID, RSA Alg, string Keyname)
    {
        if (Doc == null)
            throw new ArgumentNullException("Doc");
        if (ElementToEncrypt == null)
            throw new ArgumentNullException("Element to Encrypt");
        if (EncryptionElementID == null)
            throw new ArgumentNullException("EncryptionElementID");
        if (Alg == null)
            throw new ArgumentNullException("ALG");
        //specify which xml elements to encrypt
        XmlElement elementToEncrypt = Doc.GetElementsByTagName(ElementToEncrypt)[0] as XmlElement;

        if (elementToEncrypt == null)
            throw new XmlException("The specified element was not found");
        try
        {
            //create session key
            RijndaelManaged sessionkey = new RijndaelManaged();
            sessionkey.KeySize = 256;

            //encrypt using Encrypted exml object and hold in byte array
            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;

            edElement.EncryptionMethod = new EncryptionMethod(EncryptedXml.XmlEncAES256Url);
            //encrypt the session key and add it encrypted key 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 data reference to encrypted key

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

            edElement.KeyInfo.AddClause(new KeyInfoEncryptedKey(ek));

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



        // 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)
        {
            throw e;
        }
    }


    public static string Decrypt()
    {
            //create XML documentobject and load config file
            XmlDocument xmlDoc = new XmlDocument();

            try
            {
                xmlDoc.Load("config.xml");
            }
            catch (FileNotFoundException e)
            {
                Console.WriteLine(e.Message);
                Console.ReadLine();

            }
            catch (Exception e)
            {
                Console.WriteLine(e.Message);
                Console.ReadLine();
            }

            //create container for key
            CspParameters cspParam = new CspParameters();
            cspParam.KeyContainerName = "XML_RSA_FTP_KEY";
            cspParam.Flags = CspProviderFlags.UseMachineKeyStore;
            //create key and store in container
            RSACryptoServiceProvider ftpkey = new RSACryptoServiceProvider(cspParam);


            //add keyname mapping qnd decrypt the document
            EncryptedXml exml = new EncryptedXml(xmlDoc);
            exml.AddKeyNameMapping("ftpkey", ftpkey);
            exml.DecryptDocument();

            //pass decrypted document to extract credentials method
            string details =  Extract_Credentials(xmlDoc);

            //return decrypted log in details
            return details;

    }

Any help would be appreciated. Thanks, Darren


I changed your Encrypt function to not pass in the RSA Alg, but rather create the RSACryptoServiceProvider rsaAlg, using the string Keyname param, this should be the same string used in the Decrypt for the KeyContainerName, "XML_RSA_FTP_KEY"

The reason the Decryption functions throws "Bad Data" exception when trying to Decrypt on another PC is that the CspParameters is linked to the session on the PC where the Encryption was run.

The cspParams object will need to be embedded and encrypted in the XML to enable Decryption on another PC. Luckily there is EncryptionProperty we can use for this.

public static void Encrypt(XmlDocument Doc, string ElementToEncrypt, string EncryptionElementID, string Keyname)
    {
        if (Doc == null)
            throw new ArgumentNullException("Doc");
        if (ElementToEncrypt == null)
            throw new ArgumentNullException("Element to Encrypt");
        if (EncryptionElementID == null)
            throw new ArgumentNullException("EncryptionElementID");

        // Create a CspParameters object and specify the name of the key container.
        var cspParams = new CspParameters { KeyContainerName = Keyname }; //"XML_RSA_FTP_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.
        var rsaAlg = new RSACryptoServiceProvider(cspParams);

        //specify which xml elements to encrypt
        XmlElement elementToEncrypt = Doc.GetElementsByTagName(ElementToEncrypt)[0] as XmlElement;

        if (elementToEncrypt == null)
            throw new XmlException("The specified element was not found");
        try
        {
            //create session key
            RijndaelManaged sessionkey = new RijndaelManaged();
            sessionkey.KeySize = 256;

            //encrypt using Encrypted exml object and hold in byte array
            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;

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

            byte[] encryptedKey = EncryptedXml.EncryptKey(sessionkey.Key, rsaAlg, 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 data reference to encrypted key

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

            edElement.KeyInfo.AddClause(new KeyInfoEncryptedKey(ek));

            // Save some more information about the key using the EncryptionProperty element.

            // Create a new "EncryptionProperty" XmlElement object. 
            var property = new XmlDocument().CreateElement("EncryptionProperty", EncryptedXml.XmlEncNamespaceUrl);

            // Set the value of the EncryptionProperty" XmlElement object.
            property.InnerText = RijndaelManagedEncryption.EncryptRijndael(Convert.ToBase64String(rsaAlg.ExportCspBlob(true)),
                            "Your Salt string here");

            // Create the EncryptionProperty object using the XmlElement object. 
            var encProperty = new EncryptionProperty(property);

            // Add the EncryptionProperty object to the EncryptedKey object.
            ek.AddProperty(encProperty);

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



            // 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)
        {
            throw;
        }
    }

    public static string Decrypt()
    {
        //create XML documentobject and load config file
        XmlDocument xmlDoc = new XmlDocument();

        try
        {
            xmlDoc.Load("config.xml");
        }
        catch (FileNotFoundException e)
        {
            Console.WriteLine(e.Message);
            Console.ReadLine();

        }
        catch (Exception e)
        {
            Console.WriteLine(e.Message);
            Console.ReadLine();
        }

        //create container for key
        CspParameters cspParam = new CspParameters();
        cspParam.KeyContainerName = "XML_RSA_FTP_KEY";
        cspParam.Flags = CspProviderFlags.UseMachineKeyStore;
        //create key and store in container
        RSACryptoServiceProvider ftpkey = new RSACryptoServiceProvider(cspParam);

        var keyInfo = xmlDoc.GetElementsByTagName("EncryptionProperty")[0].InnerText;
        ftpkey.ImportCspBlob(
            Convert.FromBase64String(RijndaelManagedEncryption.DecryptRijndael(keyInfo,
                "Your Salt string here")));

        //add keyname mapping qnd decrypt the document
        EncryptedXml exml = new EncryptedXml(xmlDoc);
        exml.AddKeyNameMapping("ftpkey", ftpkey);
        exml.DecryptDocument();

        //pass decrypted document to extract credentials method
        string details = Extract_Credentials(xmlDoc);

        //return decrypted log in details
        return details;

    }

Have a look here for the RijndaelManagedEncryption class.


The obvious question is how you moved the XML_RSA_FTP_KEY private key to the server.

If you did not do anything, the decrypt()-method will have generated a new keypair in the XML_RSA_FTP_KEY-container. This key will fail to decrypt the data which was encrypted with a different key and give the "Bad data"-exception.


I received the same error when using the EncryptedXml class, X.509 certs, and I forgot to grant access to the private key of a cert to the process/task owner of the decryption process. So, don't forget to grant access to the private key!

I know that when you share web.config files amonst all servers in a web-farm which are encrypted with a custom/shared RSA-CSP key, you need to also share the key in a container to all the servers which will need to decrypt the ciphertext in your web.config. Once you import the key from the container on each server in the farm, you need to grant access to the application pool identity which you asp.net application runs as in IIS. See the -pc, -px, -pi, and -pa arguments to the aspnet_regiis.exe tool for how to create, export, import, and authorize access to RSA keys, respectively (http://msdn.microsoft.com/en-us/library/k6h9cz8h.ASPX). This is another good resource: http://msdn.microsoft.com/en-us/library/2w117ede.aspx.

To make this a bit more deterministic in practice, I ran my application in IIS under a domain service account, created a custom shared RSA key, imported across my web farm, granted access to the key to the domain service account, and then encrypted sensitive parts of the web.config with the specific key (see the -pef and -pdf arguments on aspnet_regiis.exe).

If you are encrypting application settings, you might want to consider creating a custom app.config/web.config section (if you aren't keen on encrypting all the "appSettings"). Then, encrypt it with the Right key using aspnet_regiis.exe. Last, distribute the protected web.config in your deployment process (you probably would package the encrypted web.config along with your application). The built-in configProtectionProvider which transparently encrypts/decrypts app.config and web.config sections is very handy.

Here is an example of what such an encrypted section in a file would look like:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>

 <configSections>
    <section name="secureAppSettings" type="System.Configuration.NameValueSectionHandler"/>
 </configSections>

  <configProtectedData>
    <providers>
      <add name="MyKeyProvider"
           type="System.Configuration.RsaProtectedConfigurationProvider, System.Configuration, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"
           keyContainerName="MyKey"
           useMachineContainer="true" />
    </providers>
  </configProtectedData>

  <secureAppSettings configProtectionProvider="MyKeyProvider">
    <EncryptedData Type="http://www.w3.org/2001/04/xmlenc#Element"
      xmlns="http://www.w3.org/2001/04/xmlenc#">
      <EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#tripledes-cbc" />
      <KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
        <EncryptedKey xmlns="http://www.w3.org/2001/04/xmlenc#">
          <EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#rsa-1_5" />
          <KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
            <KeyName>Rsa Key</KeyName>
          </KeyInfo>
          <CipherData>
            <CipherValue>deadbeef</CipherValue>
          </CipherData>
        </EncryptedKey>
      </KeyInfo>
      <CipherData>
        <CipherValue>cafef00d</CipherValue>
      </CipherData>
    </EncryptedData>
  </secureAppSettings>

</configuration>

As you can see, out-of-the-box config protection is using the same framework for XML EncryptedData, but just does all the crypto legwork for you. This works the same way for windows services and desktop applications, provided you grant access to private keys properly.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜