Configuration File Encryption

Background



I got the task of setting up CI. It was decided to use the transformation of configuration files and store confidential data in encrypted form. You can encrypt and decrypt them using the Key Container.



Key container



Every Windows OS has sets of generated keys. The key is generated either on the account or on the machine. The keys generated by the machine can be viewed along this path C: \ ProgramData \ Microsoft \ Crypto \ RSA \ MachineKeys. This is where the key that we will create next will go.



Key creation



We start cmd from the administrator and switch to the directory with aspnet_regiis, I have C: \ Windows \ Microsoft.NET \ Framework64 \ v4.0.30319



Execute the command



aspnet_regiis -pc "TestKey" -exp
      
      





exp - is added so that you can export the key in the future

TestKey - the name of our Key Container



Key Export



Team



 aspnet_regiis -px "TestKey" :\TestKey.xml -pri
      
      





TestKey - name Key Container

C: \ TestKey.xml - the path where the file will be exported

pri - add a private key to export



Import key



Team



 aspnet_regiis -pi "TestKey" :\TestKey.xml
      
      





TestKey - name Key Container

C: \ TestKey.xml - the path from where the file will be exported



Setting Rights



In order for your application or IIS to work with key container, you need to configure rights for it.



This is done by the team



 aspnet_regiis -pa "TestKey" "NT AUTHORITY\NETWORK SERVICE"
      
      





TestKey - name Key Container

NT AUTHORITY \ NETWORK SERVICE - who will be given access to the key



By default, IIS has ApplicationPoolIdentity for the pool.



The Microsoft documentation (see link 2) describes ApplicationPoolIdentity as:





Therefore, for IIS to be able to decrypt the config, Identity must be configured in the pool for the account, or you can select "NETWORK SERVICE" and give it rights.



Adding a section to config



 <configProtectedData defaultProvider="RsaProtectedConfigurationProvider"> <providers> <add name="CustomRsaProtectedConfigurationProvider" type="System.Configuration.RsaProtectedConfigurationProvider,System.Configuration, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" description="Uses RsaCryptoServiceProvider to encrypt and decrypt" keyContainerName="TestKey" cspProviderName="" useMachineContainer="true" useOAEP="false"/> </providers> </configProtectedData>
      
      





Also key container and RsaProtectedConfigurationProvider are defined globally in files



C: \ Windows \ Microsoft.NET \ Framework \ v4.0.30319 \ Config \ machine.config, C: \ Windows \ Microsoft.NET \ Framework64 \ v4.0.30319 \ Config \ machine.config



 <configProtectedData defaultProvider="RsaProtectedConfigurationProvider"> <providers> <add name="RsaProtectedConfigurationProvider" type="System.Configuration.RsaProtectedConfigurationProvider,System.Configuration, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" description="Uses RsaCryptoServiceProvider to encrypt and decrypt" keyContainerName="NetFrameworkConfigurationKey" cspProviderName="" useMachineContainer="true" useOAEP="false"/> <add name="DataProtectionConfigurationProvider" type="System.Configuration.DpapiProtectedConfigurationProvider,System.Configuration, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" description="Uses CryptProtectData and CryptUnProtectData Windows APIs to encrypt and decrypt" useMachineProtection="true" keyEntropy=""/> </providers> </configProtectedData>
      
      





Encryption



Encryption itself can be done in three ways.



Command Line Encryption



 aspnet_regiis.exe -pef connectionStrings :\Site -prov "CustomRsaProtectedConfigurationProvider"
      
      





C: \ Site - path to the file with the config.



CustomRsaProtectedConfigurationProvider - our provider specified in the config called key container.



Encryption through a written application



 private static string provider = "CustomRsaProtectedConfigurationProvider"; public static void ProtectConfiguration() { Configuration config = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None); ConfigurationSection connStrings = config.ConnectionStrings; if (connStrings != null && !connStrings.SectionInformation.IsProtected && !connStrings.ElementInformation.IsLocked) { connStrings.SectionInformation.ProtectSection(provider); connStrings.SectionInformation.ForceSave = true; config.Save(ConfigurationSaveMode.Full); } } public static void UnProtectConfiguration(string path) { Configuration config = ConfigurationManager.OpenExeConfiguration(path); ConfigurationSection connStrings = config.ConnectionStrings; if (connStrings != null && connStrings.SectionInformation.IsProtected && !connStrings.ElementInformation.IsLocked) { connStrings.SectionInformation.UnprotectSection(); } }
      
      





Bicycle



When there is a file transformation and you need to encrypt sections separately from the entire config, then only a self-written version is suitable. We create an instance of the RsaProtectedConfigurationProvider class, take a node from xml and encrypt it separately, then replace the original xml node with our encrypted one and save the result.



 public void Protect(string filePath, string sectionName = null) { XmlDocument xmlDocument = new XmlDocument { PreserveWhitespace = true }; xmlDocument.Load(filePath); if (xmlDocument.DocumentElement == null) { throw new InvalidXmlException($"Invalid Xml document"); } sectionName = !string.IsNullOrEmpty(sectionName) ? sectionName : xmlDocument.DocumentElement.Name; var xmlElement = xmlDocument.GetElementsByTagName(sectionName)[0] as XmlElement; var config = new NameValueCollection { { "keyContainerName", _settings.KeyContainerName }, { "useMachineContainer", _settings.UseMachineContainer ? "true" : "false" } }; var rsaProvider = new RsaProtectedConfigurationProvider(); rsaProvider.Initialize(_encryptionProviderSettings.ProviderName, config); var encryptedData = rsaProvider.Encrypt(xmlElement); encryptedData = xmlDocument.ImportNode(encryptedData, true); var createdXmlElement = xmlDocument.CreateElement(sectionName); var xmlAttribute = xmlDocument.CreateAttribute("configProtectionProvider"); xmlAttribute.Value = _encryptionProviderSettings.ProviderName; createdXmlElement.Attributes.Append(xmlAttribute); createdXmlElement.AppendChild(encryptedData); if (createdXmlElement.ParentNode == null || createdXmlElement.ParentNode.NodeType == XmlNodeType.Document || xmlDocument.DocumentElement == null) { XmlDocument docNew = new XmlDocument { InnerXml = createdXmlElement.OuterXml }; docNew.Save(filePath); } else { xmlDocument.DocumentElement.ReplaceChild(createdXmlElement, xmlElement); xmlDocument.Save(filePath); } }
      
      





References



1.docs.microsoft.com/en-us/previous-versions/53tyfkaw

2.support.microsoft.com/en-za/help/4466942/understanding-identities-in-iis



All Articles