I am trying to figure out a good way to store and load a password in the application configuration of my C# application. To achieve this I use the BouncyCastle library and the DPAPI from Windows.
To protect the password from getting decrypted by other applications just using DPAPI under the same account it got encrypted I generate entropy from a salt I generate with BouncyCastle (this is stored in the registry) and a secret that is embedded in my application. To protect the secret I thought about obfuscating the code.
In the code below first the raw password is loaded from the configuration then encrypted and wrote back to the configuration as base64 string.
private const string SECRET = "D9E789B1-0151-4DBE-91D4-361633A5C64C";
private const string REGKEY = "Software\\myCompany\\myApplication";
private const string SALT_REGKEY = "data";
private const string CERT_SECTION = "CertificateConfiguration";
private const int SALT_LENGTH = 256;
public static void EncryptPassword()
{
//Load Config
var config = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);
var section = (CertificateConfiguration)config.GetSection(CERT_SECTION);
if (section != null
&& !section.SectionInformation.IsProtected
&& !section.SectionInformation.IsLocked
&& !section.IsEncrypted)
{
byte[] shared = Encoding.Unicode.GetBytes(SECRET);
byte[] salt = GenerateSalt();
SaveSalt(salt);
var entropy = salt.Concat(shared).ToArray();
byte[] encryptedData = ProtectedData.Protect(
Encoding.Unicode.GetBytes(section.Password),
entropy,
DataProtectionScope.CurrentUser);
section.Password = Convert.ToBase64String(encryptedData);
section.IsEncrypted = true;
//Save Config
section.SectionInformation.ForceSave = true;
config.Save(ConfigurationSaveMode.Full);
}
}
private static byte[] GenerateSalt()
{
SecureRandom secureRandom = new SecureRandom(); // Do *not* seed secureRandom! Automatically seeded from system entropy.
CipherKeyGenerator keyGen = new CipherKeyGenerator();
keyGen.Init(new KeyGenerationParameters(secureRandom, SALT_LENGTH));
return keyGen.GenerateKey(); //AES key
}
private static void SaveSalt(byte[] salt)
{
var key = Registry.CurrentUser.CreateSubKey(REGKEY, RegistryKeyPermissionCheck.ReadWriteSubTree);
if (key != null)
{
key.SetValue(SALT_REGKEY, salt, RegistryValueKind.Binary);
key.Close();
}
}
In the second part the encrypted password is loaded from the configuration and used to load a certificate.
private static X509Certificate2 LoadCertificate()
{
try
{
var config = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);
var section = (CertificateConfiguration)config.GetSection(CERT_SECTION);
if (section == null)
return null;
string password = section.Password;
X509Certificate2 certificate;
using (SecureString s = new SecureString())
{
char[] chars = new char[0];
if (section.IsEncrypted)
{
var registryEntry = Registry.CurrentUser.OpenSubKey(REGKEY);
if (registryEntry != null)
{
byte[] salt = (byte[])registryEntry.GetValue(SALT_REGKEY);
byte[] shared = Encoding.Unicode.GetBytes(SECRET);
var entropy = salt.Concat(shared).ToArray();
byte[] decryptedData = ProtectedData.Unprotect(
Convert.FromBase64String(password),
entropy,
DataProtectionScope.CurrentUser);
chars = Encoding.Unicode.GetChars(decryptedData);
Array.Clear(decryptedData, 0, decryptedData.Length);
// ReSharper disable once RedundantAssignment
decryptedData = null;
}
}
else
{
chars = password.ToCharArray();
}
foreach (char c in chars)
{
s.AppendChar(c);
}
s.MakeReadOnly();
Array.Clear(chars, 0, chars.Length);
// ReSharper disable once RedundantAssignment
chars = null;
GC.Collect();
certificate = new X509Certificate2(section.CertificateLocation, s);
}
}
catch
{
return null;
}
}
What problems you see with this approach?
Are there any problems which will leave the clear password in memory longer than needed in the function that decrypts the password?
Are there any better ways to generate a salt?
Should the Secret be calculated in any form so the process gets obfuscated?