Best practice for encrypting on the client - c#

I am currently using a web API that allows an "Encryption" option.
I can setup my account to have a "shared key", and using this key i should encrypt all data on the client before submitting to the server.
Details from their website:
Encryption Algorithm: DES
Block Mode: ECB
Padding: PKCS7 or PKCS5
(they are interchangeable)
"Shared key" in this meaning i believe is a symmetric algorithm - same key used to decrypt/encrypt, although i may be wrong on this one.
I would like to know what is the best practice of handling this scenario on the client side?
If my application's logic should be using this key to encrypt data, how is it safe from a hacker ?
Note that my app is written in C#, meaning it can be decompiled practically for free.

Unless your key is compromised, then the transmission of your data is safe – anyone eavesdropping on your client–server connection would not be able to decrypt your data unless they have your key.
Your main challenge lies in the secure storage of the key locally on both the client and the server. For this end, I would suggest looking into the Windows Data Protection API (DPAPI) exposed through the ProtectedData class in .NET.

If shared key means public key then you are, most probaly, using one of the algorithms known as asymmetric encryption. This way you are safe to hacker since public key can't be used to decrypt data.
If it's symmetric then all depends on how secure key is. You can store it separately from a program (so user can store it securely on a flash drive). So each user must have it's own key, it's not possible to use one symmetric key for all.

In this manner, client will encrypt the data with different key and server will decrypt with different key. This is called asymmetric encryption/decryption.
The .NET Framework provides the RSACryptoServiceProvider and DSACryptoServiceProvider classes for asymmetric encryption. These classes create a public/private key pair when you use the default constructor to create a new instance. Asymmetric keys can be either stored for use in multiple sessions or generated for one session only. While the public key can be made generally available, the private key should be closely guarded.
For example [VB.NET]:
Dim cspParam as CspParameters = new CspParameters()
cspParam.Flags = CspProviderFlags.UseMachineKeyStore
Dim RSA As System.Security.Cryptography.RSACryptoServiceProvider
= New System.Security.Cryptography.RSACryptoServiceProvider(cspParam)
The key information from the cspParam object above can be saved via:
Dim publicKey as String = RSA.ToXmlString(False) ' gets the public key
Dim privateKey as String = RSA.ToXmlString(True) ' gets the private key
The above methods enable you to convert the public and / or private keys to Xml Strings.
And of course, as you would guess, there is a corresponding FromXmlString method to get them back.
So to encrypt some data with the Public key. The no-parameter constructor is used as we are loading our keys from XML and
do not need to create a new cspParams object:
Dim str as String = "HelloThere"
Dim RSA2 As RSACryptoServiceProvider = New RSACryptoServiceProvider()
' ---Load the private key---
RSA2.FromXmlString(privateKey)
Dim EncryptedStrAsByt() As Byte =RSA2.Encrypt(System.Text.Encoding.Unicode.GetBytes(str),False)
Dim EncryptedStrAsString = System.Text.Encoding.Unicode.GetString(EncryptedStrAsByt)
and as a "proof of concept", to DECRYPT the same data, but now using the Public key:
Dim RSA3 As RSACryptoServiceProvider = New RSACryptoServiceProvider(cspParam)
'---Load the Public key---
RSA3.FromXmlString(publicKey)
Dim DecryptedStrAsByt() As Byte =RSA3.Decrypt(System.Text.Encoding.Unicode.GetBytes(EncryptedStrAsString), False)
Dim DecryptedStrAsString = System.Text.Encoding.Unicode.GetString(DecryptedStrAsByt)

Related

How to persist private key of RSAParameter in C#/.net

Starting with .Net 4.7.2 (.Net Standard 2.0) it's possible to create self-signed certificates and certificate signing requests with C#/.Net only, see the MS Documentation.
While it's straight forward to create a self-signed certificate which will assert HasPrivateKey (you just call CreateSelfSigned(notBefore, notAfter)) I'm having a hard time to figure out how to get hold of the private key in general, e.g. if I want to create a certificate signed by a CA and then want to persist the certificate as a PFX file or want to persist the private key in a .PEM file or want to store it in the MS certificate store together with the private key, or when I just want to have in memory and also assert HasPrivateKey.
What I do have is a 'RSAParameters' instance which is in possession of the relevant private information, but I failed to figure out how to (easily) use that for the purpose in question (create a PFX file or PEM file or MS Certificate Store entry) without having to read through all the relevant RFCs and write a program for that on my own. (That RSAParameter instance contains the D, Exponent and Modulus, so I could try to patch this together (with the help of this answer, hopefully), but I was hoping for a C# method which will perform these tasks for me (which I could not find) by now).
Of course the idea is to do that with .Net functionality alone, as well.
Every hint on how to achieve this is appreciated.
If you only have Modulus, Exponent, and D you first have to recover the CRT parameters (P, Q, DP, DQ, InverseQ).
As your other questions, you're mainly missing the cert.CopyWithPrivateKey(key) extension methods and rsa.ImportParameters(RSAParameters):
if I want to create a certificate signed by a CA and then want to persist the certificate as a PFX file
using (RSA rsa = RSA.Create())
{
rsa.ImportParameters(rsaParameters);
using (X509Certificate2 caSigned = GetCASignedCert(rsa))
using (X509Certificate2 withKey = caSigned.CopyWithPrivateKey(rsa))
{
File.WriteAllBytes("some.pfx", withKey.Export(X509ContentType.Pkcs12, "and a password"));
}
}
or want to persist the private key in a .PEM file
This one is available in .NET Core 3.0 daily builds:
RSAParameters rsaParameters = default(RSAParameters);
using (StreamWriter writer = new StreamWriter("rsa.key"))
using (RSA rsa = RSA.Create())
{
rsa.ImportParameters(rsaParameters);
writer.WriteLine("-----BEGIN RSA PRIVATE KEY-----");
writer.WriteLine(
Convert.ToBase64String(
rsa.ExportRSAPrivateKey(),
Base64FormattingOptions.InsertLineBreaks));
writer.WriteLine("-----END RSA PRIVATE KEY-----");
}
PKCS#8 and encrypted PKCS#8 are also available.
On existing versions this requires using the RSAParameters and a ITU-T X.690 DER encoder.
or want to store it in the MS certificate store together with the private key
using (RSA rsa = RSA.Create())
{
rsa.ImportParameters(rsaParameters);
using (X509Certificate2 caSigned = GetCASignedCert(rsa))
using (X509Certificate2 withKey = caSigned.CopyWithPrivateKey(rsa))
using (X509Store store = new X509Store(StoreName.My, StoreLocation.CurrentUser))
{
X509Certificate2 persisted = new X509Certificate2(
withKey.Export(X509ContentType.Pkcs12, ""),
"",
X509KeyStorageFlags.PersistKeySet);
using (persisted)
{
store.Open(OpenFlags.ReadWrite);
store.Add(persisted);
}
}
}
or when I just want to have in memory and also assert HasPrivateKey.
using (RSA rsa = RSA.Create())
{
rsa.ImportParameters(rsaParameters);
using (X509Certificate2 caSigned = GetCASignedCert(rsa))
{
// Yes, this value can outlive both usings
return caSigned.CopyWithPrivateKey(rsa);
}
}
If you are using a certificate hardware security module (HSM) such as a USB key, for example, it is not possible to "get" the private key because the HSM only provides an interface for using the private key. This is for security as once a private key is in a file or memory it is potentially obtainable by a third party.
Also .NET has, historically, not presented a flexible enough interface, although it is improving. As such a many software vendors use the more complete and well-maintained, Bouncy Castle API (http://www.bouncycastle.org/csharp/) and you will find a lot of documentation around the web. Generally if .NET can't do it - bouncy castle will. Ironically an HSM requires .NET crypto access it's private key functionality on windows, but you usually encapsulate that somehow.
It is a steep learning curve using crypto APIs in general and it's unlikely that you will get much assistance without having a code example you want to make work.

Implementing Hybrid Encryption?

I already have an asymmetric algorithm implemented in an MVC C# Application, however I would like to modify the encryption method so that I make use of both symmetric and asymmetric encryption (AKA Hybrid encryption). Any idea how I can do this?
Asymmetric encrypt:
public string AsymmEncrypt(int accId, string input, string publickey)
{
Account a = new UserRepository().GetAccountById(accId);
RSACryptoServiceProvider myAlg = new RSACryptoServiceProvider();
CspParameters cspParams = new CspParameters();
publickey = new UserRepository().PublicKeyByAccountId(accId);
cspParams.KeyContainerName = publickey;
myAlg = new RSACryptoServiceProvider(cspParams);
byte[] cipher = myAlg.Encrypt(UTF8Encoding.UTF8.GetBytes(input), true);
return Convert.ToBase64String(cipher);
}
Asymmetric decrypt:
public string AsymmDecrypt(int accId, string input, string privatekey)
{
Account a = new UserRepository().GetAccountById(accId);
RSACryptoServiceProvider myAlg = new RSACryptoServiceProvider();
CspParameters cspParams = new CspParameters();
privatekey = new UserRepository().PrivateKeyByAccountId(accId);
byte[] cipher = myAlg.Decrypt(Convert.FromBase64String(input), true);
return UTF8Encoding.UTF8.GetString(cipher);
}
You should probably not try to reinvent the wheel here. The System.Security.Cryptography namespace in .net alrady provides a large array of cryptography functionality that is quite well vetted. Don't try to use your own Asymmetric functions to accomplish this.
If you want to do private key distribution through public key encryption, you should use something like RSAPKCS1KeyExchangeFormatter or maybe even RSAOAEPKeyExchangeFormatter if you have the flexibility to support PKCS#1v2
I would suggest reading how SSL or OpenPGP are implemented.
I'm not sure what part you are struggling with.
In short, the asymmetric algorithm is used for symmetric key exchange.
The symmetric algorithm is used for the bulk data (stream/block) crypto. You won't get it done with simply modifying your 2 functions, you will need to implement a handshake and key exchange.
Since you have an MVC.NET app, you can host it within a web server and gain HTTPS/SSL transport. You can also do the same with WCF. Any reason why aren't using what is provided by the underlying transport? You can even configure your application (web.config) to require client certificates.
PS: I agree about not re-inventing the wheel, even Microsoft's article that Erik linked to provides a warning about it.
Caution We recommend that you do not attempt to create your own key exchange method from the basic functionality provided, because many details of the operation must be performed carefully in order for the key exchange to be successful.

How to check if a key already exists in container?

I'm building an application for secure messaging between multiple clients. In order to achieve that, I encrypt the message with AES, then I encrypt the AES key with the recipients public RSA key, then I send these two components (RSA-encrypted AES key and AES-encrypted message) to the recipient. The whole process works well and without any errors.
Now I ran into a problem and I'm wondering what would be the best practice: In order to persist the private and public key of one participent, I need to store the key pair. Saving it somewhere as an XML file is possible, but obviously not an option. So decided to use a key container as described here. Implementing the use of the container is quite easy, but how can I
Check if a specified container already exists?
Check if the key size matches a given value?
As far as I can see this is not possible, because the RSACryptoServiceProvider generates a new key if the container does not exist - without telling so. But I need to know if there is a previously stored key pair available or if a new one is created.
How can I fix that? Or is this a completely wrong approach?
Try this:
public static bool DoesKeyExists(string containerName)
{
var cspParams = new CspParameters
{
Flags = CspProviderFlags.UseExistingKey,
KeyContainerName = containerName
};
try
{
var provider = new RSACryptoServiceProvider(cspParams);
}
catch (Exception e)
{
return false;
}
return true;
}
similiar question: How to check if RSA key container exists in .NET

Implement secure communication using RSA encryption in C#

I want to implement a scenario where two endpoints can securely communicate with each other using public/private key encryption. The scenario is following:
For A to send a message to B:
A encrypts the message using A's private key.
A encrypts the message using B's public key.
A sends the message.
B receives the message.
B decrypts the message using A's public key.
B decrypts the message using B's private key.
B reads the message.
Here is what I have in C# using RSA encryption:
// Alice wants to send a message to Bob:
String plainText = "Hello, World!";
Byte[] plainData = Encoding.Default.GetBytes(plainText);
Byte[] cipherData = null;
RSACryptoServiceProvider alice = new RSACryptoServiceProvider();
RSACryptoServiceProvider bob = new RSACryptoServiceProvider();
var alicePrivateKey = alice.ExportParameters(true);
var alicePublicKey = alice.ExportParameters(false);
var bobPrivateKey = bob.ExportParameters(true);
var bobPublicKey = bob.ExportParameters(false);
RSACryptoServiceProvider messenger = new RSACryptoServiceProvider();
messenger.ImportParameters(alicePrivateKey);
cipherData = messenger.Encrypt(plainData, true);
messenger.ImportParameters(bobPublicKey);
cipherData = messenger.Encrypt(cipherData, true);
messenger.ImportParameters(alicePublicKey);
cipherData = messenger.Decrypt(cipherData, true);
messenger.ImportParameters(bobPrivateKey);
cipherData = messenger.Decrypt(cipherData, true);
String result = Encoding.Default.GetString(alice.Decrypt(cipherData, true));
Clearly, there is something wrong with the following lines:
messenger.ImportParameters(bobPublicKey);
cipherData = messenger.Encrypt(cipherData, true);
Which throws System.Security.Cryptography.CryptographyException with message { "Bad Length" }.
As I can see it is not able to encrypt the data using just the public part of bob's key.
Can someone throw some light on how to properly accomplish what I want to do in C#?
Two problems here:
A) Your protocol design is wrong. If you want to use RSA to exchange messages, the algorithm is this:
A encrypts message using B's public key
A sends the message
B decrypts the message using B's private key
(B does processing)
B encrypts message using A's public key
B sends the message
A decrypts the message using A's private key
and so on. Notice how A does not know B's private key, and vice versa. The public and private keys are related in such a way that a message encrypted with a public key (known to everyone) can only be decrypted with the corresponding private key (known only to the intendent recipient of the encrypted message). This is the whole point of RSA, actually.
As for implementation in C#, it is quite trivial to do with the Crypto classes once you really understand the underlying concepts. See for example here and here.
B) RSA is good for exchanging small amounts of data. It is meant for key exchange over an insecure channel without the need for a shared secret. For exchanging "normal" data, a symmetric algorithm such as AES is used. So the idea would be generating a random passphrase and IV from A, and sending that to B via RSA as discussed in A; after both parties know the passphrase and IV, they can just encrypt data using AES with the shared key.
This is what SSL does, and you should have a really good reason to roll your own instead of using a standard SSL stream.
RSA is used to encrypt data which are smaller than the key. You use symmetric key to encrypt large amount of data and then use the RSA to share the symmetric key.
For further details you might refer to this question : how to use RSA to encrypt files (huge data) in C#

How do I decrypt RSA data in C#.NET appropriately?

My server creates a RSACryptoServiceProvider and exports its parameters to a variable (RSAKeyInfo).
Then, the public key is sent to the client, and the client encrypts something with that public key.
Now, I need to be able to decrypt this very data when sent back to the server - hence why RSA is useful in my case.
However, I get a "Bad Data" exception when trying to recreate a RSACryptoServiceProvider with imported parameters from the first RSACryptoServiceProvider created previously.
... Code might be clearer.
Creating the crypto:
class Cryptograph
{
public Cryptograph()
{
this.RSAKeyInfo = new RSACryptoServiceProvider(2048, new CspParameters(1)).ExportParameters(true);
}
}
Accessing it later for decryption:
byte[] encrypted = ...;
RSACryptoServiceProvider rsa = new RSACryptoServiceProvider();
rsa.ImportParameters(this.Cryptograph.RSAKeyInfo);
byte[] decrypted = rsa.Decrypt(encrypted, false);
Console.WriteLine(Utilities.ByteArrayToHexString(decrypted));
I get the "Bad Data" exception at this line:
byte[] decrypted = rsa.Decrypt(encrypted, false);
What am I doing wrong? How can I do it properly?
Thank you :)
P.S.: Please don't send MSDN or obvious Google results links, I've read all these pages and still can't get it to work.
When something is encrypted with a public key, you need to use the private key for the decryption. I don't see where you are using the private key for decryption.
I realize you have already read this, but you may want to read the Encrypt page and this Decrypt page, and make certain that you are following the steps:
http://msdn.microsoft.com/en-us/library/te15te69.aspx
Unless you are encrypting very short messages, such as a password, RSA encryption should generally be used for encrypting a symmetric key, which is faster to encrypt/decrypt longer messages.
The size of what you can encrypt with a public key is tied to the length of the key.
I needed an encryption/decryption that used no padding, and C#.NET doesn't provide it by default. OpenSSL.NET will do the job, however, I'm stuck while trying to use it. (See this question if you want to help me make it work). :(

Categories