Asymmetric Encryption in your .Net Application

For almost all applications in the cloud, there are times when the developer needs to write some code to perform encryption. Most of the scenarios can be fulfilled with a symmetric key encryption, but the key management becomes harder when the encryption and the decryption are across multiple security boundaries. The best options in such a scenario is asymmetric encryption, which would involve two keys, usually encoded in an X.509 certificate. The encryption is done using the public key, and the decryption can be done with the private key. This model works great, as anyone can have the public key, and send data to the receiver, and only the receiver who has the sole access to the private key can decrypt the data.

Performing asymmetric encryption can sometimes be confusing, and I have often seen developers doing it the wrong way, which usually is not caught in the unit test, and later fails on the production server. There are essentially three ways to perform an asymmetric encryption in your .net application.

Methods of Performing Asymmetric Encryption

Using RSACryptoServiceProvider

This RSACryptoServiceProvider class implements asymmetric encryption and decryption with RSA algorithm. This is one of the most common ways of asymmetric encryption but using this directly to encrypt a bigger size of data, would result in an error. While writing the code, and testing it in a unit test, the developer might miss that if he uses a small size of data to encrypt for testing.

If you execute this code to encrypt:
byte[] data = File.ReadAllBytes(“ABigFileFile.txt”) //Load a 700 KB file
byte[] encryptedData = null;
using (RSACryptoServiceProvider rsaCrytoProvider = new RSACryptoServiceProvider())
{
RSAParameters publickey = rsaCrytoProvider.ExportParameters(false);
rsaCrytoProvider.ImportParameters(publickey);
encryptedData = rsaCrytoProvider.Encrypt(data, true);
}

The code will throw a CryptographicException with the following stack:

System.Security.Cryptography.CryptographicException: Bad Length.
at System.Security.Cryptography.CryptographicException.ThrowCryptographicException(Int32 hr)
at System.Security.Cryptography.RSACryptoServiceProvider.EncryptKey(SafeKeyHandle pKeyContext, Byte[] pbKey, Int32 cbKey, Boolean fOAEP, ObjectHandleOnStack ohRetEncryptedKey)
at System.Security.Cryptography.RSACryptoServiceProvider.Encrypt(Byte[] rgb, Boolean fOAEP)

This message is usually hard to understand, but essentially this means, the data you are trying to encrypt exceeds the maximum permitted size.

So, by rule asymmetric encryption is designed to encrypt a limited size of data, and the size it can encrypt depends on the key size. The max number of bytes which can be encrypted by RSA for a specific key size can be explained as:

((RSA_Key_Size – 384) / 8) + 37

However, with the padding, the size further decreases. For example, with the OAEP padding parameter set to true in the Encrypt method, the maximum size becomes:

((RSA_Key_Size – 384) / 8) + 7

If you would like to dig deeper into the mathematics of asymmetric algorithm, to understand the reason behind it, I would recommend you pick a nice book on cryptography., My personal favorite is Applied Cryptography by Bruce Schneier.

If you would like to use the RSACryptoServiceProvider to encrypt a data with bigger size, the recommended approach is to use the hybrid method. The hybrid method encrypts the data with a randomly generated symmetric key, and then use the above method to encrypt only the symmetric key. The encrypted symmetric key can be sent along with the data to the intended party.

The code to decrypt data using RSACryptoServerProvider is:

string plainText = null;
using (RSACryptoServiceProvider rsaCrytoProvider = new RSACryptoServiceProvider())
{
RSAParameters privateKey = rsaCrytoProvider.ExportParameters(true);
rsaCrytoProvider.ImportParameters(privateKey);
byte[] decryptedData = rsaCrytoProvider.Decrypt(data, true);
plaintext = System.Text.Encoding.ASCII.GetString(decryptedData);
}

Using EnvelopedCMS

The above method of using the hybrid method works well, but it’s a lot of work to create the symmetric key, and then encrypt it and attached to the message. The intended recipient would need to know the format that is being used to send the data and the key and write code to decrypt accordingly. Or, you can use the .net provided EnvelopedCMS class to abstract away this process. The EnvelopedCMS class essentially does exactly what I described, and outputs a standard format that can be decrypted using the same EnvelopedCMS class, without the need for the intended party to understand the format. This essentially creates an envelope contains the encrypted data and all the related information that is required to decrypt the data by the EnvelopedCMS.Decrypt method. The EnvelopedCMS class can be configured to add multiple public keys, for multiple recipients to receive the data and decrypt with their own private key, using the CmsRecipient class.

The code to encrypt data using EnvelopedCMS is:

var dataBytes = Encoding.UTF8.GetBytes(parameterPlainText);
ContentInfo contentInfo = new ContentInfo(dataBytes);
EnvelopedCms ecms = new EnvelopedCms(contentInfo);
CmsRecipient cmsRecipient = new CmsRecipient(encryptionCertificate);
ecms.Encrypt(cmsRecipient);
byte[] envelopedMessage = ecms.Encode();
return Convert.ToBase64String(envelopedMessage);

And the code to decrypt:

var cipherBytes = Convert.FromBase64String(parameterCipherText);
EnvelopedCms ecms = new EnvelopedCms();
ecms.Decode(cipherBytes);
ecms.Decrypt(ecms.RecipientInfos[0], new X509Certificate2Collection(decryptionCertificate));
byte[] plainTextData = ecms.ContentInfo.Content;
return Encoding.UTF8.GetString(plainTextData);

Using Native Windows Crypto API (CAPI)

The above code actually uses, the CryptEncrypt and CryptDecrypt of the Windows Native Crypto API (CAPI) implementation. So, it is also possible to P/Invoke the AdvApi32.dll library to make native calls to these APIs to perform the asymmetric encryptions and decryptions. You can use the below DllImport in your .net application to call those APIs.

[DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool CryptEncrypt(IntPtr hKey, IntPtr hHash, int Final, uint dwFlags, byte[] pbData, ref uint pdwDataLen, uint dwBufLen);

[DllImport("advapi32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool CryptDecrypt(IntPtr hKey, IntPtr hHash, int Final, uint dwFlags, byte[] pbData, ref uint pdwDataLen);

Security Considerations using Asymmetric Encryption

Data Integrity

It is imperative to understand that, encryption without integrity check doesn’t provide any security. If you perform an encryption for someone with their public key and send it over the network, the data can be intercepted and replaced with data by the malicious user and re-encrypt with the public key. This defies the whole purpose of the encryption in the first place. The receiver, in this case, will never know, the data has been changed, and will think the data was from the original source. This is known as a man-in-the-middle attack. So, its very important to sign the data with the sender’s private key, which can be verified by the receiver with the sender’s public key, to maintain its integrity. The public key can be shared by the sender with the receiver, through a different channel or could be sent along with the data.

Here is how to sign your data using RSACryptoServiceProvider using an SHA-256 hash algorithm:

var cert = new X509Certificate2(certPath, "password");
var csp = (RSACryptoServiceProvider)cert.PrivateKey;
var signature = csp.SignData(data, CryptoConfig.MapNameToOID("SHA256"));

And to verify the signature using RSACryptoServiceProvider

var cert = new X509Certificate2(certPath, "password");
var csp = (RSACryptoServiceProvider)cert.PublicKey.Key;
var isValid = csp.VerifyData(date, CryptoConfig.MapNameToOID("SHA256"), signature);

Signing Certificate Verification

A lot of time, the signing X.509 certificate is generated by the sender without it being signed by any trusted certificate authority (CA). These certificates are called a self-signed certificate. This is because, getting a certificate signed by a trusted certificate authority generally cost money, and unless this is being used by an enterprise or a website, people try to avoid it. Without this, the man-in-the-middle attacker would generate a self-signed certificate with the same subject name and change the data and re-sign the packet with that. The receiver would verify the signature of the data and would trust it as valid. There are two ways to mitigate this situation:

  1. Always use a CA-signed certificate for signing. It costs money, and it might be worth to pay that money. The receiver could trust the CA, and its verification step will only succeed if the signature is valid, and the signer certificate is issued by one of the trusted CA.
  2. If you must use a self-signed certificate, please ensure the receiver whitelists the thumbprint or the public key of the certificate. In this case, the receiver verifies the signature and ensures the signer certificate thumbprint or public key is on its trusted whitelist. But, this also creates a little bit of a management issue, when the sender needs to rotate the certificate. The sender would need to communicate the new thumbprint to the receiver through a very secure channel.

Certificate/Public Key Pinning

When a certificate is signed by a certificate authority (CA), there are generally more than one CAs involved. It would be signed by one or more intermediate certificate authority, and finally would be signed by a root certificate authority. For example, the Azure portal certificate has the following signing hierarchy:

CertCAChain

The intermediate CA are generally the CAs the enterprise would own, and the root is one of the known public CA (e.g. DigiCert, Verisign, Baltimore CyberTrust, GoDaddy etc.). When the certificate is verified for authenticity, the receiver would run this chain of trust and would call the certificate valid, if one of the CA in the chain is trusted. But, if you think a bit more about it, with just the chain trust checking, it could pose a serious threat. The attacker could get a certificate issued by one of these public root CA and intercept the message on the network to replace and re-sign with his own certificate. The receiver would trust the certificate when looking up the chain, as everyone usually trusts the root CA. To mitigate this risk, and to enhance the security of the verification process, it is recommended to “pin” or whitelist the thumbprint or the public key of one of the intermediate CA. This is called as certificate/public key pinning. Usually, this also means the receiver has established a trust relationship with the sender or a group of senders. The receiver would perform a two-step validation, where at the first step it will verify the chain, and at the second step, it will check it against its whitelist. If they need to whitelist a set of CA/certificates for a host sender, they can do so, and would be called as “pinset”.

There are some considerations that need to be taken while using certificate pinning. If in the rare situation where the pinned intermediate CA or the top-level root needs to be changed, there is a high chance that all the certificate under that chain would have to be re-generated and re-pinned to make them acceptable. This is usually a huge task, for enterprises who owns hundreds and sometimes thousands of certificates. This has happened not too long ago when Google stopped supporting Chinese based WoSign root CA in its Chrome browser.

9 thoughts on “Asymmetric Encryption in your .Net Application”

  1. Excellent blog here! Also your website so much up very fast! What web host are you using? Can I am getting your affiliate hyperlink to your host? I want my website loaded up as quickly as yours lol

    Like

  2. I’m not certain where you’re getting your information, but good topic. I must spend a while learning more or working out more. Thank you for fantastic info I was looking for this info for my mission.

    Like

  3. You actually make it appear so easy along with your presentation however I to find this topic to be actually one thing that I think I would by no means understand. It seems too complicated and very wide for me. I’m looking ahead to your subsequent post, I will try to get the hold of it!

    Like

  4. Thanks for any other great article. Where else could anybody get that type of information in such a perfect approach of writing? I have a presentation next week, and I am at the look for such information.

    Like

  5. excellent put up, very informative. I wonder why the opposite experts of this sector don’t understand this. You must proceed your writing. I am confident, you’ve a great readers’ base already!

    Like

Leave a comment