RSA Decryption using private key between two systems - c#

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);

Related

How do I use the private key from a PFX certificate stored in Azure Key Vault in .NET Core 2?

I've written an ASP.NET Core 2.0 website in C# and have Facebook authentication enabled, so it requires HTTPS. I'm using the native Kestrel web server to host the site and have a listener set to take the PFX certificate per MS' documentation. I can't seem to find a way for Kestrel to recognize the private key after recall from Key Vault. I know it's present, as I wrote two debug statements that indicate it is, in fact present.
This is the function that I'm using to retrieve the secret, which is working.
public static async Task<X509Certificate2> GetKeyVaultCert()
{
X509Certificate2 pfx;
try
{
var kvClient = new KeyVaultClient(new KeyVaultClient.AuthenticationCallback(GetToken));
var secret = await kvClient
.GetSecretAsync("https://macscampvault.vault.azure.net/secrets/letsencrypt").ConfigureAwait(false);
byte[] bytes;
if(secret.ContentType == "application/x-pkcs12")
bytes = Convert.FromBase64String(secret.Value);
else
{
bytes = new byte[0];
Console.WriteLine("secret is not PFX!!");
throw new ArgumentException("This is not a PFX string!!");
}
var password = new SecureString();
var coll = new X509Certificate2Collection();
coll.Import(bytes, null, X509KeyStorageFlags.Exportable);
pfx = coll[0];
// File output added in case I end up needing to write cert to container
// File.WriteAllBytes(Directory.GetCurrentDirectory().ToString() + "/Macs.pfx", bytes);
Console.WriteLine(pfx.HasPrivateKey);
Console.WriteLine(pfx.GetRSAPrivateKey());
}
catch (Exception ex)
{
Console.WriteLine($"There was a problem during the key vault operation\n{ex.Message}");
throw;
}
return pfx;
}
The debug statements after the assignment call pfx = coll[0]; tell me that this private key exists, but when I try to connect to the website using lynx https://localhost I receive the following exception:
System.NotSupportedException: The server mode SSL must use a certificate with the associated private key.
So, how do I use the private key? Here's a gist to the file in question.
I already was helped by How to serialize and deserialize a PFX certificate in Azure Key Vault? but after following it, I got to this state.
In your gist you have the following code:
var keyVaultCert = GetKeyVaultCert().Result ??
throw new ArgumentNullException("GetKeyVaultCert().Result");
pfx = new X509Certificate2(keyVaultCert.RawData);
The second line there removes the private key, because the RawData property just returns the DER encoded X.509 object.
keyVaultCert is already an X509Certificate2 with a private key, you probably want to just use it.
pfx = GetKeyVaultCert().Result ?? throw etc;

An exception of type 'System.Security.Cryptography.CryptographicException': keyset does not exist

All the steps made at this link System.Security.Cryptography.CryptographicException: keyset does not exist
But it did not help to correct the error.
static public string Build64(string idOrder, double Amount) {
string StrForSign = KKBRequestStr.Replace("%ORDER%", idOrder).Replace("%AMOUNT%", string.Format("{0:f}", Amount).Replace(",", "."));
X509Certificate2 KKbCert = new X509Certificate2(KKBpfxFile, KKBpfxPass);
RSACryptoServiceProvider rsaCSP = (RSACryptoServiceProvider)KKbCert.PublicKey.Key;
byte[] SignData = rsaCSP.SignData(ConvertStringToByteArray(StrForSign), "SHA1"); // keyset does not exist!!!!!!!!
Array.Reverse(SignData);
string ResultStr = "<document>" + StrForSign + "<merchant_sign type=\"RSA\">" + Convert.ToBase64String(SignData, Base64FormattingOptions.None) + "</merchant_sign></document>";
return Convert.ToBase64String(ConvertStringToByteArray(ResultStr), Base64FormattingOptions.None);
}
As I understand, you are trying to sign some data using RSA having only a public key.
RSA signing is a process of document verification. You sign a document using private key and then use public key to check if it is really yours. In other words, you cannot sign document with public key.
That's why you get error "keyset does not exist". Your keyset doesn't contain a private key for signing data.
You need to extract the private key from your KkbCert and use it for signing.

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.

.Net Crypto Service Provider error using nShield HSM

I am trying to use nShield from Thales to generate pair of asymmetric keys on it.
I have found the following example on msdn:
CspParameters csp = new CspParameters(1, "eToken Base Cryptographic Provider");
csp.Flags = CspProviderFlags.UseDefaultKeyContainer;
try
{
RSACryptoServiceProvider rsa = new RSACryptoServiceProvider(csp);
key = rsa.ToXmlString(true);
}
catch(Exception ex )
{
string s = ex.Message;
}
I can use KeySafe to succesfully connect and generate key-pairs on the HSM.
The code above throws the following exception:
System.Security.Cryptography.CryptographicException
"Invalid Signature." System.Security.Cryptography.CryptographicException
I have the feeling that I am not setting the correct second parameter in the CspParameters constructor. This is what it says in the example:
// The 1st parameter comes from HKEY_LOCAL_MACHINE\Software\Microsoft\Cryptography\Defaults\Provider Types.
// The 2nd parameter comes from HKEY_LOCAL_MACHINE\Software\Microsoft\Cryptography\Defaults\Provider.
I don't see any nCipher or nShield or Thales or anything like that there.
Edit:
Working test:
RSACryptoServiceProvider rsa = new RSACryptoServiceProvider(csp);
byte[] data = Encoding.ASCII.GetBytes("string");
byte[] enc = rsa.Encrypt(data, false);
String dec = Encoding.ASCII.GetString(rsa.Decrypt(enc, false));
key = rsa.ToXmlString(true);
You need to run nCipher CSP install wizard which is located under Start > All Programs > nCipher in order to register nCipher CSP in your operating system. After that mentioned registry entries will be available and you will be able to read exact CSP name from them.

Where to store secret key of the c# application

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
);

Categories