Implementing RSA asymmetric public-private key encryption in C#: encrypting under the public key
Following on from my last post on how to generate a public / private key pair in C#, this is the next post in my series on using RSA asymmetric encryption in .Net.
Now we have a public / private key pair, we can encrypt an arbitrary string using RSA encryption. To make this code more general, we are going to allow users to specify the bit length of the public key, allowing us to easily encrypt under 1024, 2048 and 4096 bit keys. To this end, I am reusing the RsaKeyLengths enumeration from the last post containing three common bit lengths: 1024, 2048 and 4096.
Performing RSA Encryption is not particularly difficult, but neither is it straightforward.
The basic steps are as follows:
- Instantiate a new RSACryptoServiceProvider object using the bit length used to generate the public key. These bit lengths must match: you cannot provide a 1024 bit public key and then attempt to encrypt the data using 2048 bits for example.
- Initialize the RSACryptoServiceProvider object from the public key using the FromXmlString method. Remember that the public key is serialized as XML (see my post on generating public / private key pairs if uncertain)
- Convert the data to encrypt to a byte array
- Work out the maximum length of data that can be encrypted per block. This requires a bit of knowledge of how the RSACryptoServiceProvider works, specifically that it uses the SHA1 hash function internally.
- Encrypt the data block by block.
As we allowing encryption of arbitrary strings, we may not be able to encrypt our data in a single block. We encrypt the data block by block until it is completely encrypted. - Reverse the array of the encrypted bytes in the block.
<sarcasm>Microsoft have never before been known to implement an incompatible version of a standard process but there’s always a first time</sarcasm>: the RSACryptoServiceProvider reverses the order of the encrypted bytes after encryption and again before decryption which makes our completed encrypted string look like gibberish to other implementations, so we need to undo this before we continue. - Concatenate the encrypted blocks and return the encrypted string
/// <summary>
/// Encrypt an arbitrary string of data under the supplied public key
/// </summary>
/// <param name="publicKey">The public key to encrypt under</param>
/// <param name="data">The data to encrypt</param>
/// <param name="length">The bit length or strength of the public key: 1024, 2048 or 4096 bits. This must match the
/// value actually used to create the publicKey</param>
/// <returns></returns>
public static string Encrypt(string publicKey, string data, RsaKeyLengths length)
{
// full array of bytes to encrypt
byte[] bytesToEncrypt;
// worker byte array
byte[] block;
// encrypted bytes
byte[] encryptedBytes;
// length of bytesToEncrypt
var dataLength = 0;
// number of bytes in key
var keySize = 0;
// maximum block length to encrypt
var maxLength = 0;
// how many blocks must we encrypt to encrypt entire message?
var iterations = 0;
// the encrypted data
var encryptedData = new StringBuilder();
// instantiate the crypto provider with the correct key length
var rsaCryptoServiceProvider = new RSACryptoServiceProvider((int) length);
// initialize the RSA object from the given public key
rsaCryptoServiceProvider.FromXmlString(publicKey);
// convert data to byte array
bytesToEncrypt = Encoding.Unicode.GetBytes(data);
// get length of byte array
dataLength = bytesToEncrypt.Length;
// convert length of key from bits to bytes
keySize = (int)length / 8;
// .NET RSACryptoServiceProvider uses SHA1 Hash function
// use this to work out the maximum length to encrypt per block
maxLength = ((keySize - 2) - (2 * SHA1.Create().ComputeHash(bytesToEncrypt).Length));
// how many blocks do we need to encrypt?
iterations = dataLength / maxLength;
// encrypt block by block
for (int index = 0; index <= iterations; index++)
{
// is there more than one full block of data left to encrypt?
if ((dataLength - maxLength * index) > maxLength)
{
block = new byte[maxLength];
}
else
{
block = new byte[dataLength - maxLength * index];
}
// copy the required number of bytes from the array of bytes to encrypt to our worker array
Buffer.BlockCopy(bytesToEncrypt, maxLength * index, block, 0, block.Length);
// encrypt the current worker array block of bytes
encryptedBytes = rsaCryptoServiceProvider.Encrypt(block, true);
// RSACryptoServiceProvider reverses the order of encrypted bytesToEncrypt after encryption and before decryption.
// Undo this reversal for compatibility with other implementations
Array.Reverse(encryptedBytes);
// convert to base 64 string
encryptedData.Append(Convert.ToBase64String(encryptedBytes));
}
return encryptedData.ToString();
}
The RsaKeyLengths enumeration used to request 1024, 2048 or 4096 bit encryption:
public enum RsaKeyLengths
{
Bit1024 = 1024,
Bit2048 = 2048,
Bit4096 = 4096
}
I learned a lot from this article, great help for me, thank you!
Jeff Machine
August 20, 2011 at 11:10
hi, how will we handle the encryption if the external party has given us their public key (not in xml format)
do we have to convert the public key in an xml format?
anant
September 6, 2012 at 17:56
There are several methods of conveying public key data and in order to use FromXmlString yes the public key will have to be in the expected XML format. I can’t remember off the top of my head whether RSACryptoServiceProvider implements any methods to load keys in non-XML format.
andrewlocatelliwoodcock
September 6, 2012 at 18:43
i have the public key, can i directly put that in the XML format…putting public key between the Modulus tag….something like this
Public key will come hereAQAB
anant
September 6, 2012 at 20:40
I noticed that your code appears to be copied from a 2007 Code Project article with minor changes. See http://www.codeproject.com/Articles/10877/Public-Key-RSA-Encryption-in-C-NET
Peter
October 4, 2012 at 15:42
Hi Peter,
thanks for the accusation of plagiarism. The code is taken my 2006 / 2007 Masters thesis for the University of Liverpool on supplying high-quality random numbers via web services for which I was awarded a first class MSc degree. Draw whatever conclusions you want.
Andrew
andrewlocatelliwoodcock
October 4, 2012 at 15:59
Nice post Andrew and it was really useful. Sharing this to my community through my daily digest.
Kannan Subbiah
January 14, 2013 at 02:15
Hi,
This is an excellent work. This implementation works well on larger text. Then how do I decrypt it? any implementation available?
Selva
Selva
February 8, 2013 at 07:59
Hi,
Excellent work, then It is encrypting a larger text. then how do I decrypt the text?
Regards,
Selva
Selva
February 8, 2013 at 09:14
Hi Selva – thanks for the comment. Apologies but the follow-up has been on my to-do list for a while now … I will try to get round to it shortly …
andrewlocatelliwoodcock
February 12, 2013 at 10:01