One of the applications I’m working on has the need to store Privately Identifiable Information (PII) in it’s databases. We have chosen to store this information using the RSA Public Key Cryptography provider included in the .NET Core base libraries.
We all use Public Key Cryptography (PKC) every day. If you are reading this you are using PKC as the underpinnings of HTTPS use public key cryptography. Basically PKC is implemented using two individual keys the PUBLIC one and the PRIVATE one. The public key is used to encrypt data and the private key is used to decrypt data. As it name implies you can distribute the PUBLIC key … well… publicly. Meaning you can give it out freely. The PRIVATE key is yours and yours alone and is the only key that can decrypt your data.
For our needs we created a simple library for managing our PKC needs. This library has 3 parts:
1) Function to generate PUBLIC and PRIVATE keys 2) Function to encrypt a string 3) Function to decrypt a string.
Lets take a look at generating keys first. To to this you need to create a class that will be used to return the PUBLIC and PRIVATE key strings. The class looks like this:
public class KeyPair
{
public string PublicKey { get; set; } = "";
public string PrivateKey { get; set; } = "";
}
Next you need to create an instance of the RSACryptoServiceProvider class. When creating an instance of this class you need to pass it the size of your keys.
The size of the key specifies the strength of the key you are using. Meaning how difficult will it be to crack. The larger the more difficult it is to crack. Our code stores the size of the key in a CONST called Keysize. After creating an instance of the provider your an call the ToXmlString() of the provide which will return your PUBLIC and PRIVATE keys depending on the boolean you pass to the function. Passing a false value returns the PUBLIC key, passing a true value returns the private key. The following code shows how to create and return a PUBLIC/PRIVATE key pair.
using System;
using System.Security.Cryptography;
using System.Text;
namespace CryptoLibrary
{
public class CryptoTools
{
private const int KeySize = 2048;
public KeyPair GetKeyPair()
{
var provider = new RSACryptoServiceProvider(KeySize);
var retVal = new KeyPair()
{
PublicKey = provider.ToXmlString(false),
PrivateKey = provider.ToXmlString(true)
};
return retVal;
}
public class KeyPair
{
public string PublicKey { get; set; } = "";
public string PrivateKey { get; set; } = "";
}
}
Now you can call the library to generate your keypair.
var tools = new CryptoTools();
var keys = tools.GetKeyPair();
This code returns your keys in an XML string that looks like this:
<RSAKeyValue>
<Modulus>vx53GKAPG02.......yQgyoVlAwYHZxP7jVTyQ==</Modulus>
<Exponent>AQAB</Exponent>
</RSAKeyValue>
With these two keys you can write some simple functions to Encrypt and Decrypt strings. The steps to encrypt a string are:
1) Create an instance of the RSACryptoServiceProvider class. 2) Load the PUBLIC key using the FromXmlString() function. 3) Convert your string to a byte[] array 4) Encrypt the string (returned as byte[] array) 5) Turn the byte[] array into a base 64 string. 6) Return the string.
The steps to decrypt a string are:
1) Create an instance of the RSACryptoServiceProvider class. 2) Load the PRIVATE key using the FromXmlString() function. 3) Convert your string to a byte[] array 4) Decrypt the string (returned as byte[] array) 5) Turn the byte[] array into a UTF8 string. 6) Return the string.
The following code represents this functionality:
public string Encrypt(string publicKeyXML, string itemToEncrypt)
{
var provider = new RSACryptoServiceProvider(KeySize);
provider.FromXmlString(publicKeyXML);
byte[] bytes = Encoding.UTF8.GetBytes(itemToEncrypt);
var encryptedData = provider.Encrypt(bytes,true);
var retval = Convert.ToBase64String(encryptedData);
return retval;
}
public string Decrypt(string privateKeyXML, string itemToDecrypt)
{
var provider = new RSACryptoServiceProvider(KeySize);
provider.FromXmlString(privateKeyXML);
byte[] bytes = Convert.FromBase64String(itemToDecrypt);
var encryptedData = provider.Decrypt(bytes, true);
var retval = Encoding.UTF8.GetString(encryptedData);
return retval;
}
The following code shows how to generate keys, encrypt the data and descrypt the data.
var tools = new CryptoTools();
var keys = tools.GetKeyPair();
Console.WriteLine(keys.PublicKey);
Console.WriteLine(keys.PublicKey);
var encrypted = tools.Encrypt(keys.PublicKey, "555-12-3456");
var decrypted = tools.Decrypt(keys.PrivateKey, encrypted);
var encrypted2 = tools.Encrypt(keys.PublicKey, "4000-0000-0000-0002");
var decrypted2 = tools.Decrypt(keys.PrivateKey, encrypted);
The complete library for these tools:
using System;
using System.Security.Cryptography;
using System.Text;
namespace CryptoLibrary
{
public class CryptoTools
{
private const int KeySize = 2048;
public KeyPair GetKeyPair()
{
var provider = new RSACryptoServiceProvider(KeySize);
var retVal = new KeyPair()
{
PublicKey = provider.ToXmlString(false),
PrivateKey = provider.ToXmlString(true)
};
return retVal;
}
public string Encrypt(string publicKeyXML, string itemToEncrypt)
{
var provider = new RSACryptoServiceProvider(KeySize);
provider.FromXmlString(publicKeyXML);
byte[] bytes = Encoding.UTF8.GetBytes(itemToEncrypt);
var encryptedData = provider.Encrypt(bytes,true);
var retval = Convert.ToBase64String(encryptedData);
return retval;
}
public string Decrypt(string privateKeyXML, string itemToDecrypt)
{
var provider = new RSACryptoServiceProvider(KeySize);
provider.FromXmlString(privateKeyXML);
byte[] bytes = Convert.FromBase64String(itemToDecrypt);
var encryptedData = provider.Decrypt(bytes, true);
var retval = Encoding.UTF8.GetString(encryptedData);
return retval;
}
}
public class KeyPair
{
public string PublicKey { get; set; } = "";
public string PrivateKey { get; set; } = "";
}
}
I hope that you find these tools useful. In my next post I’ll show how to combine this solution and the hashing library shown in my Building a Simple C# Hashing Utility post to build a bit more holistic solution to handling encrypted data.