This is the 2nd part in a small series on using encryption primitives in .NET. In the first article I concentrated on symmetric cryptography and more specifically the AES algorithm. In this article I will take a brief look at Asymmetric cryptography using the RSA system.

RSA stands for Ron Rivest, Adi Shamir and Leonard Adleman
RSA stands for Ron Rivest, Adi Shamir and Leonard Adleman

RSA is an algorithm for public-key cryptography that is based on the presumed difficulty of factoring large integers, the factoring problem.  RSA stands for Ron Rivest, Adi Shamir and Leonard Adleman, who first publicly described the algorithm in 1977. Clifford Cocks, an English mathematician, had developed an equivalent system in 1973, but it was classified until 1997.

A user of RSA creates and then publishes the product of two large prime numbers, along with an auxiliary value, as their public key. The prime factors must be kept secret. Anyone can use the public key to encrypt a message, but with currently published methods, if the public key is large enough, only someone with knowledge of the prime factors can feasibly decode the message. Whether breaking RSA encryption is as hard as factoring is an open question known as the RSA problem.

The AES symmetric process is classed as an algorithm where the plain text goes through multiple computation rounds to produce the cipher text. RSA is different in that is it a mathematical process. I won’t go into too much detail of how the keys are generated, but as stated above it is all around the complexity of factoring large prime numbers. The actual encryption process is based around modular arithmetic. For more detailed information on how this works check out this very useful Wikipedia page.

Public / Private Key Encryption Process
Public / Private Key Encryption Process

RSA keys are generated in pairs, the public key and private key. In essence the public key contains the result of 2 very large prime numbers multiplied together and the private key contains the 2 primes individually. The public key (as the name suggests) is public and anyone can have this. The private key has to be kept secret.

As the diagram shows above, the plain text is encrypted using the public key. Then the recipient of the message uses the secret private key to decrypt the message. This works because of the mathematical relationship between the two keys.

To use RSA in .NET is actually very straight forward. I will show some code snippets first and then include the entire class at the bottom of the post.

The code snippet below shows how to generate a pair of RSA keys.

     public void AssignNewKey(string publicKeyPath, string privateKeyPath)
        {
            if (string.IsNullOrEmpty(publicKeyPath))
            {
                throw new ArgumentNullException("publicKeyPath");
            }

            if (string.IsNullOrEmpty(privateKeyPath))
            {
                throw new ArgumentNullException("privateKeyPath");
            }

            using (RSACryptoServiceProvider rsa = new RSACryptoServiceProvider(1024))
            {
                rsa.PersistKeyInCsp = false;

                if (FileProxy.Exists(privateKeyPath) == true)
                {
                    FileProxy.Delete(privateKeyPath);
                }

                if (FileProxy.Exists(publicKeyPath) == true)
                {
                    FileProxy.Delete(publicKeyPath);
                }

                string publicKey = rsa.ToXmlString(false);
                FileProxy.Write(publicKeyPath, publicKey);
                FileProxy.Write(privateKeyPath, rsa.ToXmlString(true));
            }
        }

This method takes 2 file paths to write out the keys. To generate the key pairs you create an instance of the RSACryptoServiceProvider class and pass in a key length. In this example the key length is 1024bits which is fine. To really future proof yourself I would recommend using 2048bits.

Once this class has been instantiated, you extract the keys by calling ToXmlString() on the RSA object. Pass in false and you get an XML fragment that represents the public key and pass in true to get the private key.

In this example I am persisting the private key myself, which for the purpose of this example is fine, but normally you would want to store the private key in windows own key store or on a hardware key storage device like a HSM (Hardware Security Module). I force this not to happen in the example above with the line rsa.PersistKeyInCSP = false. The CSP stands for the Crypto Service Provider. To use the CSP, when you construct the RSACryptoServiceProvider class, instead of just passing in the key length parameter you pass in an object called the CspParameters.

        public static void AssignParameter()
        {
            const int PROVIDER_RSA_FULL = 1;
            const string CONTAINER_NAME = "MyContainer";
            CspParameters cspParams = new CspParameters(PROVIDER_RSA_FULL);
            cspParams.KeyContainerName = CONTAINER_NAME;
            cspParams.Flags = CspProviderFlags.UseMachineKeyStore;
            cspParams.ProviderName = "Microsoft Strong Cryptographic Provider";
            rsa = new RSACryptoServiceProvider(cspParams);
        }

This will tell the provider how to persist the key. In the example above it uses the standard windows key store. If you are using a Hardware Security Module, then you will have most likely installed a Crypto Service Provider driver when you installed your HSM. You will be able to access this driver using a ProviderName as in the example above.

To encrypt some data you do the following. In this example I am mainly working with strings, but you can change it to just be byte arrays by removing the Encoding.UTF8.GetBytes() and Convert.ToBase64String() calls.

     public string EncryptData(string publicKeyPath, string data2Encrypt)
        {
            if (string.IsNullOrEmpty(publicKeyPath))
            {
                throw new ArgumentNullException("publicKeyPath");
            }

            if (string.IsNullOrEmpty(data2Encrypt))
            {
                throw new ArgumentNullException("data2Encrypt");
            }

            byte[] cipherbytes;

            using (RSACryptoServiceProvider rsa = new RSACryptoServiceProvider(1024))
            {
                rsa.PersistKeyInCsp = false;

                rsa.FromXmlString(FileProxy.Read(publicKeyPath));

                byte[] plainbytes = Encoding.UTF8.GetBytes(data2Encrypt);
                cipherbytes = rsa.Encrypt(plainbytes, false);
            }

            return Convert.ToBase64String(cipherbytes);
        }

In the code above we pass in the path to the public key and the data to encrypt. The method creates an instance of the RSACryptoServiceProvider object and then loads the key from file and pushes it into the rsa instance with the FromXmlString property. Then you just simple call rsa.Encrypt() by passing in your plain text.

The decrypt your data is very similar.

        public string DecryptData(string privateKeyPath, string data2Decrypt)
        {
            if (string.IsNullOrEmpty(privateKeyPath))
            {
                throw new ArgumentNullException("privateKeyPath");
            }

            if (string.IsNullOrEmpty(data2Decrypt))
            {
                throw new ArgumentNullException("data2Decrypt");
            }

            byte[] plain;

            using (RSACryptoServiceProvider rsa = new RSACryptoServiceProvider(1024))
            {
                rsa.PersistKeyInCsp = false;

                byte[] encodedCipherText = Convert.FromBase64String(data2Decrypt);
                rsa.FromXmlString(FileProxy.Read(privateKeyPath));
                plain = rsa.Decrypt(encodedCipherText, false);
            }

            return Encoding.UTF8.GetString(plain);
        }

As with the encrypt example you pass in a private key path and the data to decrypt. You create an instance of the RSACryptoServiceProvider class, load the key and then call rsa.Decrypt().

As you can see from this example, although RSA is a complex mathematical process, .NET makes using RSA in your code very easy indeed. RSA does have some limitations though. Firstly, as it is mathematical in nature, the encryption and decryption process is quite slow as it is dealing with very large numbers. Also you are limited in the amount of data you can process at once. The size of the data has to be less than the size of the key. RSA is never really used for encrypting large amounts of data, but a common usage is for encrypting symmetric (AES) keys. As you will see in a future article, Symmetric (AES) and Asymmetric (RSA) algorithms are used hand in hand together.

In the next article I will cover Hashing and random number generation. This will give us practical examples of all the cryptographic primitives we need to perform what is called Hybrid Encryption, which is a really fascinating subject.

Here is the complete code for this example .NET RSA class.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography;
using System.Text;

namespace CryptoLibrary
{
    public sealed class RSA : IRSA
    {

        public RSA()
        {
            FileProxy = new FileProxy();
        }

        public RSA(IFileProxy fileProxy)
        {
            if (fileProxy == null)
            {
                throw new ArgumentNullException("fileProxy");
            }

            this.FileProxy = fileProxy;
        }

        public IFileProxy FileProxy { get; set; }

        public void AssignNewKey(string publicKeyPath, string privateKeyPath)
        {
            if (string.IsNullOrEmpty(publicKeyPath))
            {
                throw new ArgumentNullException("publicKeyPath");
            }

            if (string.IsNullOrEmpty(privateKeyPath))
            {
                throw new ArgumentNullException("privateKeyPath");
            }

            using (RSACryptoServiceProvider rsa = new RSACryptoServiceProvider(1024))
            {
                rsa.PersistKeyInCsp = false;

                if (FileProxy.Exists(privateKeyPath) == true)
                {
                    FileProxy.Delete(privateKeyPath);
                }

                if (FileProxy.Exists(publicKeyPath) == true)
                {
                    FileProxy.Delete(publicKeyPath);
                }

                string publicKey = rsa.ToXmlString(false);
                FileProxy.Write(publicKeyPath, publicKey);
                FileProxy.Write(privateKeyPath, rsa.ToXmlString(true));
            }
        }

        public string EncryptData(string publicKeyPath, string data2Encrypt)
        {
            if (string.IsNullOrEmpty(publicKeyPath))
            {
                throw new ArgumentNullException("publicKeyPath");
            }

            if (string.IsNullOrEmpty(data2Encrypt))
            {
                throw new ArgumentNullException("data2Encrypt");
            }

            byte[] cipherbytes;

            using (RSACryptoServiceProvider rsa = new RSACryptoServiceProvider(1024))
            {
                rsa.PersistKeyInCsp = false;

                rsa.FromXmlString(FileProxy.Read(publicKeyPath));

                byte[] plainbytes = Encoding.UTF8.GetBytes(data2Encrypt);
                cipherbytes = rsa.Encrypt(plainbytes, false);
            }

            return Convert.ToBase64String(cipherbytes);
        }

        public string DecryptData(string privateKeyPath, string data2Decrypt)
        {
            if (string.IsNullOrEmpty(privateKeyPath))
            {
                throw new ArgumentNullException("privateKeyPath");
            }

            if (string.IsNullOrEmpty(data2Decrypt))
            {
                throw new ArgumentNullException("data2Decrypt");
            }

            byte[] plain;

            using (RSACryptoServiceProvider rsa = new RSACryptoServiceProvider(1024))
            {
                rsa.PersistKeyInCsp = false;

                byte[] encodedCipherText = Convert.FromBase64String(data2Decrypt);
                rsa.FromXmlString(FileProxy.Read(privateKeyPath));
                plain = rsa.Decrypt(encodedCipherText, false);
            }

            return Encoding.UTF8.GetString(plain);
        }
    }
}
Participate with Coding in the Trenches on Facebook
Participate with Coding in the Trenches on Facebook by Click the button above.
Advertisements

6 comments

  1. Nice article!I’m trying out your code, but it seems you’re using a custom FileProxy class which is not included. I can work around it, but perhaps you can facilitate?

  2. Nice article. I am trying to determine if there is a way to control the size of the exponent when generating license keys. It seems that by default the exponent size is 4 bytes and I am trying to have an exponent with 5 bytes (seems like the latest iOS version requires that).

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s