austior
austior

Reputation: 123

Using RsaProtectedConfigurationProvider to perform encryption on strings not in a config file

My organization requires that sensitive connection strings be encrypted using a particular System.Configuration.RsaProtectedConfigurationProvider located on our production servers. However, I have an application which stores connection strings in a database and I would like to perform encryption on these with the same keys.

My original thought was to create a dummy configuration file containing a reference to the encryption provider, load it with some text and perform the encryption without having to write it back to disk. I could then pull the cipher/plaintext out of the xml:

DummyConfig.config:

<?xml version="1.0" encoding="utf-8"?>
<configuration>
 <configSections>
  <section name="DummySection" type="virutalConfig.DummySect, virutalConfig, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
 </configSections>
 <DummySection />
 <configProtectedData>
  <providers>
   <add name="MyProvider"
    type="System.Configuration.RsaProtectedConfigurationProvider, System.Configuration, Version=2.0.0.0,&#xD;&#xA; Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a,&#xD;&#xA; processorArchitecture=MSIL"
    keyContainerName="MyKeys"
    useMachineContainer="true" />
  </providers>
 </configProtectedData>
</configuration>

Program.cs:

class Program
    {
        static void Main(string[] args)
        {
            var fileMap = new ExeConfigurationFileMap
            {
                ExeConfigFilename = @"c:\virtconfigtest\DummyConfig.config"
            };

            var config = ConfigurationManager.OpenMappedExeConfiguration(fileMap, ConfigurationUserLevel.None);
            var sect = config.GetSection("DummySection") as DummySect;
            sect.Inf = "this is some plaintext!";
            sect.SectionInformation.ProtectSection("MyProvider");
            //sect.SectionInformation.ForceSave = true;
            //config.Save();

            if (sect.SectionInformation.IsProtected)
            {
                Console.WriteLine("Section is protected. Raw XML:");
                Console.WriteLine(sect.SectionInformation.GetRawXml());
            }
            else
            {
                Console.WriteLine("Section is not protected. Raw XML:");
                Console.WriteLine(sect.SectionInformation.GetRawXml());
            }

            Console.ReadLine();
        }
    }

    public class DummySect : ConfigurationSection
    {
        public DummySect() { }

        [ConfigurationProperty("inf")]
        public string Inf
        {
            get { return (string)this["inf"]; }
            set { this["inf"] = value; }
        }
    }

Unfortunately, GetRawXml() only returns the plaintext, and even to get this, it seems the config file must be written back to disk.

I could get what I want by reading the file off the disk as an xml document, but I'd rather not have to do that. The whole plan is pretty gross anyway, even without adding disk writes into the mix. Will I have to retrieve the RSA keys from the provider to do this? If so, how?

Upvotes: 1

Views: 1499

Answers (1)

austior
austior

Reputation: 123

With some research and trial and error, I did eventually figure out how to do this.

Here's what the encrypted part of a config file looks like:

 <DummySection configProtectionProvider="MyProvider">
  <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>RsMpDD/wJmmpN+Mme+qFuRVm2Ddk759hWM7HaeAnW7xpfkCoC4ko7vDBmqylzQ0QAFL2wuR8u8Bsf+4xwn++Ru/GsEaYrGrcDMYJTuWElyHuxnw+5umqexQJye2R5uL/91alFVNV41HnSPlwuA+pgk14yHSWIflIyKFmUTx58vU=</CipherValue>
     </CipherData>
    </EncryptedKey>
   </KeyInfo>
   <CipherData>
    <CipherValue>lQI7gyQZ2HIIQUdKsp73HrYcebbOiO4dCriwCt5avfVTcxPZEHzaCfV52k+triRwq64uGVCNRpGUe5PCVEfbWwrPHaNaFzRp</CipherValue>
   </CipherData>
  </EncryptedData>
 </DummySection> 

The first block of cipher text is a triple DES key ecrypted with RSA. The keypair can be obtained from a file found in the directory: C:\ProgramData\Microsoft\Crypto\RSA\MachineKeys

The second part is encrypted with this DES key and prepended with a 64 bit initialization vector. Frustratingly, it is padded with the method: ISO10126

Here is the code to do the decryption:

        var cspParameters = new CspParameters()
        {
             KeyContainerName = "MyKeys",
             Flags = CspProviderFlags.UseMachineKeyStore
        }; //refers to a file in the machine keys directory

        var rsaKey = new RSACryptoServiceProvider(cspParameters);
        var t1 =
            Convert.FromBase64String(
                "RsMpDD/wJmmpN+Mme+qFuRVm2Ddk759hWM7HaeAnW7xpfkCoC4ko7vDBmqylzQ0QAFL2wuR8u8Bsf+4xwn++Ru/GsEaYrGrcDMYJTuWElyHuxnw+5umqexQJye2R5uL/91alFVNV41HnSPlwuA+pgk14yHSWIflIyKFmUTx58vU=");
        var t2 =
            Convert.FromBase64String(
                "lQI7gyQZ2HIIQUdKsp73HrYcebbOiO4dCriwCt5avfVTcxPZEHzaCfV52k+triRwq64uGVCNRpGUe5PCVEfbWwrPHaNaFzRp");
        var desKey = rsaKey.Decrypt(t1, false); //get the des key
        var iv = t2.Take(8).ToArray(); //get the initialization vector
        var ct = t2.Skip(8).ToArray(); //get the actual ciphertext

        var desEnc = new TripleDESCryptoServiceProvider()
        {
            Padding = PaddingMode.ISO10126
        };

        var plaintext = Encoding.Default.GetString(desEnc.CreateDecryptor(desKey, iv).TransformFinalBlock(ct, 0, ct.Length));

Upvotes: 1

Related Questions