Where to store secret key of the c# application - c#

There are similar questions
How to Manage Key in a Symmetric Algorithm
Where to store a secret key to use in a SHA-1 hash?
My question is same, But I want to ask it differently
I have C# application. I am encrypting some data in the application. For encryption I am using secret key or password. This same thing is needed for decryption.
Where/how to store this secret key or password in application? its easy to view string password from reflection. I may use some combination to generate password, but some smart guys can guess that with some efforts.
Is there any secured way to store or manage secret password which is used in application to encrypt data?

I doubt there is any guaranteed secure way to store the key. Ultimately your program has to get access to the key, and a cracker could easily work out how that is happening via reverse engineering and redirect that string to wherever they want to.
Your best options are to:
Obfuscate the key as much as possible. This makes it more difficult to access the "secret key" but does not make it impossible (see above). Rather than storing it as a string, generate it using a function, or use a seed and pass that through a function to get the secret string.
If your use case allows it, use a public/private key pair. It only works if you want your application to encrypt the data, send it to your servers, and then you want to decrypt it. In this case, you embed the public key into the application (doesn't matter if crackers discover that), and keep the private key to yourself or your server.

If you store the key as an app-setting, and encrypt the app-settings, then I think you're pretty save.
You can use the following code to encrypt sections of the app.config.
using System;
using System.Configuration;
public static class ConfigurationEncryptor {
[Flags]
public enum ConfigurationSectionType {
ConnectionStrings = 1,
ApplicationSettings = 2
}
/// <summary>
/// Encrypts the given sections in the current configuration.
/// </summary>
/// <returns>True is the configuration file was encrypted</returns>
public static bool Encrypt(ConfigurationSectionType section) {
bool result = false;
Configuration config = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);
if (config == null)
throw new Exception("Cannot open the configuration file.");
if (section.HasFlag(ConfigurationSectionType.ConnectionStrings)) {
result = result || EncryptSection(config, "connectionStrings");
}
if (section.HasFlag(ConfigurationSectionType.ApplicationSettings)) {
result = result || EncryptSection(config, "appSettings");
}
return result;
}
/// <summary>
/// Encrypts the specified section.
/// </summary>
/// <param name="config">The config.</param>
/// <param name="section">The section.</param>
private static bool EncryptSection(Configuration config, string section) {
ConfigurationSection currentSection = config.GetSection(section);
if (currentSection == null)
throw new Exception("Cannot find " + section + " section in configuration file.");
if (!currentSection.SectionInformation.IsProtected) {
currentSection.SectionInformation.ProtectSection("DataProtectionConfigurationProvider");
config.Save();
// Refresh configuration
ConfigurationManager.RefreshSection(section);
return true;
}
return false;
}
}
And use it like this (e.g. in your Main() method):
ConfigurationEncryptor.Encrypt(
ConfigurationEncryptor.ConfigurationSectionType.ApplicationSettings |
ConfigurationEncryptor.ConfigurationSectionType.ConnectionStrings
);

Related

Certificate uploaded to Azure Key Vault has different content when downloading as a secret

I uploaded a .pfx certificate to the Azure Key Vault using the Azure portal. The certificate is 3558 bytes in size. When I download the certificate as a secret using C#:
public async Task SaveCertificateAsync(string key)
{
var secret = await _secretClient.GetSecretAsync(key);
var bytes = Convert.FromBase64String(secret.Value.Value);
File.WriteAllBytes("C:\\certificates\\test.pfx", bytes);
}
I get another 3558 byte file. This file however does not have the same structure as the original certificate and does not work in my application. I know that when using the GetCertificateAsync method I would get the certificate without the private key but I thought that by downloading it as a secret I would get the original, valid certificate file. Why has it changed?
I use the following code to download the Certificate from Azure Key Vault as a secret and turn it into a X509Certificate2 certificate. Do upload/import your cert to AKV as a certificte.
/// <summary>
/// Helper method to get a certificate
///
/// Source https://github.com/heaths/azsdk-sample-getcert/blob/master/Program.cs
/// </summary>
/// <param name="certificateClient"></param>
/// <param name="secretClient"></param>
/// <param name="certificateName"></param>
/// <returns></returns>
private static X509Certificate2 GetCertificateAsync(CertificateClient certificateClient,
SecretClient secretClient,
string certificateName)
{
KeyVaultCertificateWithPolicy certificate = certificateClient.GetCertificate(certificateName);
// Return a certificate with only the public key if the private key is not exportable.
if (certificate.Policy?.Exportable != true)
{
return new X509Certificate2(certificate.Cer);
}
// Parse the secret ID and version to retrieve the private key.
string[] segments = certificate.SecretId.AbsolutePath.Split('/', StringSplitOptions.RemoveEmptyEntries);
if (segments.Length != 3)
{
throw new InvalidOperationException($"Number of segments is incorrect: {segments.Length}, URI: {certificate.SecretId}");
}
string secretName = segments[1];
string secretVersion = segments[2];
KeyVaultSecret secret = secretClient.GetSecret(secretName, secretVersion);
// For PEM, you'll need to extract the base64-encoded message body.
// .NET 5.0 preview introduces the System.Security.Cryptography.PemEncoding class to make this easier.
if ("application/x-pkcs12".Equals(secret.Properties.ContentType, StringComparison.InvariantCultureIgnoreCase))
{
byte[] pfx = Convert.FromBase64String(secret.Value);
return new X509Certificate2(pfx);
}
throw new NotSupportedException($"Only PKCS#12 is supported. Found Content-Type: {secret.Properties.ContentType}");
}

What is the Encryption method use in DpapiProtectedConfigurationProvider?

I want to generate a cypher text by using DpapiProtectedConfigurationProvider.
All the codes I am seeing on the internet are do this inside a app.config. I know it is the reason this is originally build for. But I have a different usage. I have interface where user has to enter the text in a textbox and with a a click of button I need to generate the cpher text by using DpapiProtectedConfigurationProvider. How to achieve this?
Currently I am generating this inside the app.config by using following code. But this is not what I want
Configuration config = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);
//Configuration config = ConfigurationManager.OpenExeConfiguration(exefilePath);
ConfigurationSection section = config.GetSection(sectionKey);
if (section != null)
{
if (section.ElementInformation.IsLocked)
{
Console.WriteLine("Section: {0} is locked", sectionKey);
}
else
{
if (!section.SectionInformation.IsProtected)
{
//%windir%\system32\Microsoft\Protect\S-1-5-18
section.SectionInformation.ProtectSection("DataProtectionConfigurationProvider");
section.SectionInformation.ForceSave = true;
Console.WriteLine("Encrypting: {0} {1}", section.SectionInformation.Name, section.SectionInformation.SectionName);
}
else
{ // display values for current config application name value pairs
//
section.SectionInformation.UnprotectSection();
section.SectionInformation.ForceSave = true;
Console.WriteLine("Decrypting: {0} {1}", section.SectionInformation.Name, section.SectionInformation.SectionName);
}
}
}
else
{
Console.WriteLine("Section: {0} is null", sectionKey);
}
//
config.Save(ConfigurationSaveMode.Full);
Console.WriteLine("Saving file: {0}", config.FilePath);
How I do this?
The DpapiProtectedConfigurationProvider class "uses the Windows built-in cryptographic services and can be configured for either machine-specific or user-account-specific protection."
Depending on the flags used, data encrypted using DPAPI can only be decrypted by code running on the same machine where it was encrypted, or code running under the same user account where it was encrypted.
The precise algorithm used may vary depending on the version of Windows being used. From what I can see, the CryptProtectData function uses either 3DES or AES256:
c# - Which encryption algorithm does the ProtectData class use? - Stack Overflow
c# - Which Encryption algorithm does ProtectedData use? - Stack Overflow

C# - Looking for Encryption/Decryption Method

I've written a C# piece that encrypts/decrypts a string using RtlEncryptMemory/RtlDecryptMemory. This string is then saved in a config file, it all works well but the problem is that once I logoff/logon, I can no longer decrypt the string. I am using the RTL_ENCRYPT_OPTION_SAME_LOGON option which means the internal mechanism uses something from the Windows session in order to perform the decryption. I am looking for a solution that works in the same manner but is tied to the network user (or token, etc...). Is Windows providing something already?
My goal is to be able to decrypt the string from anywhere as long as the process is running under the same user (network credentials). I also do not want to have the user type in a password or use an internal value as that could be compromised. Ideally it would be just like the RTL functions but provide an RTL_ENCRYPT_OPTION_SAME_USER option.
You want to use the DataProtection API
Here is a simple implementation that adds Encrypt and Decrypt string extensions...
public static class StringExtensions
{
public static string Encrypt(this string s)
{
if (String.IsNullOrEmpty(s))
{
return s;
}
else
{
var encoding = new UTF8Encoding();
byte[] plain = encoding.GetBytes(s);
byte[] secret = ProtectedData.Protect(plain, null, DataProtectionScope.CurrentUser);
return Convert.ToBase64String(secret);
}
}
public static string Decrypt(this string s)
{
if (String.IsNullOrEmpty(s))
{
return s;
}
else
{
byte[] secret = Convert.FromBase64String(s);
byte[] plain = ProtectedData.Unprotect(secret, null, DataProtectionScope.CurrentUser);
var encoding = new UTF8Encoding();
return encoding.GetString(plain);
}
}
}
Here is an example...
class Program
{
static void Main(string[] args)
{
string password = "Monkey123";
string encrypted = password.Encrypt();
Console.WriteLine($"Encrypted password = '{encrypted}'");
string decrypted = encrypted.Decrypt();
Console.WriteLine($"Decrypted password = '{decrypted}'");
}
}
Which produces this output...
Encrypted password = 'AQAAANCMnd8BFdERjHoAwE/Cl+sBAAAA/6wDgM21DkStrNJQ35QDiwAAAAACAAAAAAAQZgAAAAEAACAAAAAPr3/aqafbt/RRoPVe75b+PFBhE6h9MLcQ2Ivsd3adOwAAAAAOgAAAAAIAACAAAABYxqEdzotL+7qXpWnbbpPRkfWZF6oh/meFsXzFtLPnrBAAAAB59VGbboP4Tye1N3dB7E3jQAAAAMQn8cAlnTDe1mwDEJriADizdT2Qr0DtPgpMje+rbjdkVpL+cKiEQs4om4i1hlLPgPn5MG5oVWFFnxU0d4c9TFg='
Decrypted password = 'Monkey123'
Notes:
Only the currently logged in user can decrypt the data encrypted with this code. This works across the network as long as the current user has a roaming profile.
Alternatively the scope can be local machine in which case only users logged in to the same machine can decrypt the data.
This is .NET Core 3.1 code and works only on Windows machines
Using statements...
using System;
using System.Security.Cryptography;
using System.Text;
You should not be using RtlEncryptMemory if you want to store the string, it is meant to only keep strings secure inside the running applications memory, it therefore can be stored/serialized and decrypted.
Have a look at DPAPI password encryption I think it should meet your needs.
I have a Nuget package you might like:
DataJuggler.Net.Cryptography .Net Framework
DataJuggler.Core.Cryptography Dot Net Core
Pretty simple to work with, here is a live demo:
https://blazorcrypto.datajuggler.com/
Source code and video link is available above also.
Usage:
Encryption:
// get the encryptedText
encryptedResult = CryptographyHelper.EncryptString(textToEncrypt, keyCode);
Decryption:
// get thedecryptedText
decryptedResult = CryptographyHelper.DecryptString(textToDecrypt, keyCode);
It also includes password hashing.
Let me know if you think it is worth the price of free.

RSA Decryption using private key between two systems

I am developing an encryption decryption software. I used RSA encryption to encypt my symmetric key.
I followed the code provided in Walkthrough: Creating a Cryptographic Application
My encryption and decryption done successfully in same machine. But when I tried to decrypt from other computer, an error: bad data is occurring.(It can be decrypted from same machine.)
I think the problem is on getting private key from keycontainer. How to get the private key generated in first machine in the second machine.
I googled a lot but everything in same machine.
Please help me, give me an idea to get private key in other machine.
public void GetPrivateKey()
{
string c;
cspp.KeyContainerName = keyName;
rsa = new RSACryptoServiceProvider(cspp);
rsa.PersistKeyInCsp = true;
if (rsa.PublicOnly == true)
c= "Key: " + cspp.KeyContainerName + " - Public Only";
else
c = "Key: " + cspp.KeyContainerName + " - Full Key Pair";
}
public string decryptkey(string at)
{
byte[] KeyEncrypted;
KeyEncrypted = File.ReadAllBytes(at);
//System.IO.File.ReadAllBytes(at);//for good
objr.GetPrivateKey();
byte[] KeyDecrypted = objr.rsa.Decrypt(KeyEncrypted, false);
string skey = GetString(KeyDecrypted);
return skey;
}
Bad data Error happens in this line,
byte[] KeyDecrypted = objr.rsa.Decrypt(KeyEncrypted, false);.
Please..
Use the RSACryptoServiceProvider.ToXmlString method to export the private key. You need to pass true to this method to export the private key. This will generate for you an XML document that contains the key parameters including the private parameters.
On the second machine, use RSACryptoServiceProvider.FromXmlString to import the private key into a RSACryptoServiceProvider instance.
However, for security reasons, I recommend that instead of doing this, generate the private key on one machine (the machine that will do the decryption part), and then use the RSACryptoServiceProvider.ToXmlString and pass false to it to just export the public key. On the other machine (that will do the encryption part), import the public key using the RSACryptoServiceProvider.FromXmlString method.
Using the public key alone, you can do the encryption part of the process.
It is only for decryption that you are required to have the private key.
Here is some sample code:
//Do this on one machine
RSACryptoServiceProvider rsa_machine1 = new RSACryptoServiceProvider(); //You might initialize this in a different way
var xml = rsa_machine1.ToXmlString(true); //or pass false to just export the public key
Now take the value of the xml variable to the other machine (maybe by saving it to a file and then manually copying that file to the second machine)
//This is done on the second machine
RSACryptoServiceProvider rsa_machine2 = new RSACryptoServiceProvider();
rsa_machine2.FromXmlString(xml);

Unable to Decrypt data on second computer

I have two applications, Server and the Client, one running from one machine, and the other from a second machine, the server is passing data using a WebSocket connection, the data is encrypted before is sent to the Client, the data makes it to the Client application correctly but I'm trying to Decrypt it using the same secure method, and Secret Key, but I won't work, it only decrypts it when both apps are run from the same computer. Does any one have any idea why it works when they are run from the same machine, but not when running them from separate machines?
Both Server and Client application use this same Secure Method.
using System.Security.Cryptography;
// ENCRYPT
static byte[] entropy = System.Text.Encoding.Unicode.GetBytes("MY SECRET KEY HERE");
public static string EncryptString(System.Security.SecureString input)
{
byte[] encryptedData = System.Security.Cryptography.ProtectedData.Protect(
System.Text.Encoding.Unicode.GetBytes(ToInsecureString(input)),
entropy,
System.Security.Cryptography.DataProtectionScope.CurrentUser);
return Convert.ToBase64String(encryptedData);
}
public static SecureString DecryptString(string encryptedData)
{
try
{
byte[] decryptedData = System.Security.Cryptography.ProtectedData.Unprotect(
Convert.FromBase64String(encryptedData),
entropy,
System.Security.Cryptography.DataProtectionScope.CurrentUser);
return ToSecureString(System.Text.Encoding.Unicode.GetString(decryptedData));
}
catch
{
return new SecureString();
}
}
public static SecureString ToSecureString(string input)
{
SecureString secure = new SecureString();
foreach (char c in input)
{
secure.AppendChar(c);
}
secure.MakeReadOnly();
return secure;
}
public static string ToInsecureString(SecureString input)
{
string returnValue = string.Empty;
IntPtr ptr = System.Runtime.InteropServices.Marshal.SecureStringToBSTR(input);
try
{
returnValue = System.Runtime.InteropServices.Marshal.PtrToStringBSTR(ptr);
}
finally
{
System.Runtime.InteropServices.Marshal.ZeroFreeBSTR(ptr);
}
return returnValue;
}
// ENCRYPT ENDS
To Encrypt data on the Server I use:
string encryptedMessage = EncryptString(ToSecureString("Data to Encrypt Here"));
To Decrypt data on the Client I use:
SecureString data1 = DecryptString(dataEncryptedReceived);
IntPtr stringPointerData1 = Marshal.SecureStringToBSTR(data1);
string normalStringData1 = Marshal.PtrToStringBSTR(stringPointerData1);
Marshal.ZeroFreeBSTR(stringPointerData1);
Again, this all works fine ONLY when I use both Server and Client applications from the same computer, but I try to use them separate, Server on one machine, and Client on another it won't Decrypt the data, even though the Client receives the encrypted data successfully.
Please help!
Thanks.
You are using System.Security.Cryptography.ProtectedData class that uses Data Protection API (DPAPI) under the hood. DPAPI encryption keys are always unique on each computer therefore when you encrypt data on computer A you are using key A and when you try to decrypt the data on the computer B you are using the key B. DPAPI provides interface to symmetric cipher only so in order to decrypt the data successfully you need to use exactly the same key for both encryption and decryption.
I believe you should change your code to use different encryption algorithm i.e. AES (implemented by System.Security.Cryptography.AesManaged class) that will allow you to share the key between two different machines.
The Protect and Unprotect methods are only making calls to the DPAPI, which only works across computers if you have roaming profiles enabled, and only then under certain circumstances.
Instead, use a algorithm with a session key which you manage yourself (AES, others...), or better yet: use TLS as your WebSocket (wss://) or Socket transport (SslStream). Rolling your own crypto is just asking for trouble.

Categories