Dustin Horne

Developing for fun...

Asymmetric Encryption and Signing with RSA in Silverlight

While Silverlight is a powerful tool for rich client applications, it lacks the ability to perform asymmetric encryption out of the box.  In this article, I'm going to share a cryptography class library I've been working on and show you how to use it to perform standards compliant RSA Encryption in Silverlight that is cross compatible with .NET's built in RSACryptoServiceProvider, allowing you to encrypt from Silverlight using my library and decrypt on your website using the RSACryptoServiceProvider.  For brevity, only examples using my class library will be shown except for a few examples that show equivelant functionality from the RSACryptoServiceProvider (RSACSP).

Update 11/24/2010: The Scrypt library has been updated.  Key generation is now performed Asynchronously to avoid blocking the UI thread and freezing the browser.  I've updated the applicable source samples in this article to reflect the changes.

Edit*:  I've decided to open up the source for this project. You can download this library and/or source and view the current applicable license on its new home at CodePlex: http://scrypt.codeplex.com/

Background

Before I get into the sample code, I'm going to give you a little bit of background.

What does it all mean?
RSA is an encryption scheme that uses a public and private key.  There are a variety of uses for RSA.  The two most common are encryption to protect data, and signing to verify the authenticity of data.  Encryption is performed with the public key, with the premise that data encrypted with the public key can only be decrypted using the private key.  The private key should be kept safe and secure and the public key can be shared with everyone.  Signing works the opposite direction and is used to verify the source of data. 

To build a signature, the data is first hashed and then encrypted using the private key.  For verification, the hashed data is decrypted using the public key, and the original value is hashed and compared to the decrypted hash.  If the values match, the data is considered to be verified.  While the actual process of constructing the signed data is a bit more involved, this illustrates the basic premise behind it.  Signing does not provide security for your data, but since the private key is required to produce the signed value, the data can be determined to be verified as long as your private key has been kept secret as a hacker will not be able to reproduce the signed value.

Key Size, Padding and Security
While RSA itself is fairly secure, there are some other considerations that should be taken into account.  RSA allows you to specify your key size, or cipher strength.  The stronger the cipher, the more security is provided by the encryption.  RSA operates by performing a series of mathematical operations that begin with two very large pseudoprime numbers.  What makes RSA secure is the inherent difficulty in factoring those numbers.  If a hacker were able to determine those two primes, that hacker could then reproduce your public and private keys and gain access to your data.  The smaller your key size, the smaller those prime numbers will be and the easier it will be for a hacker to break your key.

Let's say for instance that we used two rediculously small prime numbers, 11 and 17 as our values.  The hacker will only have to generate keys with a small number of primes before they are able to successfully guess the two prime values that were used.  By utilizing extremely large primes, RSA makes this process too time consuming to complete.  For instance, if a key size of 1024-bit is specified, the two primes being used will actually be 512-bit numbers.  For a 2048-bit key, they will be 1024-bit numbers.  This is part of what makes RSA encryption a slow and intensive process, however it also makes it secure.  The current recommended encryption strength to use for secure data is 2048-bit.

In addition to the key size, different message padding implementations have been developed.  The potential drawback of not using padding for RSA encryption is that an attacker can use your public key to start encrypting their own known data.  With enough rounds of encrypting different known data, the attacker can begin to determine part of your originally encoded data, and eventually all of it.  To combat this, padding schemes have been developed.  Padding schemes serve a couple of main purposes:

  1. Ensure that your data is always of a fixed length.  For instance, if your key is 1024 bits, the data to be encrypted will always end up being 1024 bits.  This prevets the attacker from knowing the original length of the data you are encrypting.
  2. Add a degree of randomness.  Good padding schemes produce random padding that is added to your data before it is encrypted.  This means that you could encrypt your data many times with the same key and the encrypted result would be different every time.

There are a few different padding standards in existence today.  One of the older standards, and the one that is used by default in Microsoft's RSACSP is the PKCS#1 padding scheme.  While good, it provides less security than more sophisticated padding schemes such as the currently recommended OAEP padding.

Compatibility with .NET's RSACryptoServiceProvider
My RSACrypto class has been built to be compatible with the .NET implementation.  By default, the RSACrypto class uses OAEP padding, but this can be changed using the PaddingProvider property.  While .NET's implementation simply takes a True or False value indicating whether to use OAEP padding, my class uses an instance that implements IPaddingProvider.  This allows the class to be extensible by adding additional padding providers in the future.  For example, I have included 3 different padding providers, PKCS1v1_5, which is the standard PKCS padding implementation, OAEP which is the standard OAEP implementation, and OAEP256 which is based on the standard OAEP implementation but uses a SHA256 hash in the padding generation instead of the default SHA1.  OAEP256 is not compatible with the .NET implementation as it only supports PKCS#1 v1.5 and OAEP.

Similarly, I have included a few different hashing providers that implement an IHashProvider interface.  I have included the hashing algorithims provided by Silverlight:  SHA1, SHA256, HMACSHA1 and HMACSHA256.  The HMAC versions allow you to specify a seed, or private value to use in your hash as well.  The Hash Providers are used in the Signing and Verification of data.  For full compatibility with the .NET framework's RSACSP, either SHA1 or SHA256 should be used.  The .NET implementation takes a string value indicating the type of hash to use. 

For instance, to sign data with a SHA1 hash, you would use:
oRSA.SignData(dataBytes, "SHA1"). 

With my class, you would use:
oRSA.SignData(dataBytes, new SignatureProviders.EMSAPKCS1v1_5_SHA1)

The EMSAPKCS1v1_5_SHA1 Signature Provider uses the SHA1 Hash Provider internally.

And Now...The Code

Now that you have a litte background on RSA, it's time to look at how we implement the RSACrypto class.  The code below illustrates how to use the most common functions of the RSACrypto class.  Once you've added a reference to the DH.Scrypt.dll assembly in your Silverlight project and added an Import / Using for the RSA Namespace you will be able to follow along with the code below.

Creating an Instance
The RSACrypto class currently has two constructors.  The first, an empty constructor, initializes the class with the default cipher strength of 1024 bits.  The second allows you to specify the cipher strength.  The supplied cipher strength must be a multiple of eight and RSACrypto currently supports keys in the range of 256-bit to 4096-bit.  Below is an example of creating an instance of the RSACrypto class that will generate a 2048-bit key.  If you are loading a key from an external source, such as XML, it is not necessary to specify the key size:

 C#

RSACrypto oRSA = new RSACrypto(2048);

VB.NET

Dim oRSA As New RSACrypto(2048)

  

Configuring RSACrypto Properties
The RSACrypto class currently has only two configurable properties.  The first, PaddingProvider, specifies which padding provider to use during encryption.  The second, UseThreads, specifies whether key generation should be performed as a multi-threaded task.  By default, OAEP is used as the padding provider and UseThreads is "True".  UseThreads is recommended for all keys larger than 512-bit as the key generation is a CPU intensive process.  Unless you know you won't receive a performance gain from using multi-threaded key generation, it is recommended to leave this at the default value.

C#

oRSA.PaddingProvider = new PaddingProviders.PKCS1v1_5();

VB.NET

oRSA.PaddingProvider = New PaddingProviders.PKCS1v1_5 

 

Generating Keys
New RSA key pairs can be generated using the GenerateKeys() method.  The GenerateKeys() method also has two additional overloads, allowing you to override the cipher strength the class was initialized with, and to specify your own public exponent value.  The public exponent is a prime number that must also be co-prime with another calculated value in the RSA algorithm.  Common numbers used for the public exponent range between 3 and 65537.  Lower exponents (such as 3) pose a greater security risk.  In almost every case, you should avoid setting your own exponent value and allow the default value to be used.  If you do supply your own public exponent value, it may be automatically adjusted by the RSACrypto class to the closest value compatible with the RSA algorithm.  Key generation is performed asynchronously.  Attempting to perform data operations while a key generation is in progress will result in an exception.  Below are examples of calling the GenerateKeys method and its overloads.

Defining an Event Handler to handle the completion of key generation:

C#

private void KeysGenerated(Object sender)
{
     RSACrypto oRSA = (RSACrypto)sender;
}

VB.NET

Private Sub KeysGenerated(ByVal sender As Object)
     Dim oRSA As RSACrypto = DirectCast(sender, RSACrypto)
End Sub

 

Attaching the KeysGenerated event handler and generating keys:

C#

int cipherStrength = 2048;
int pubExponent = 17;

//Attach the KeysGenerated event handler
oRSA.OnKeysGenerated += KeysGenerated;

//Use default values
oRSA.GenerateKeys();
//Override the cipher strength
oRSA.GenerateKeys(cipherStrength);
//Override cipher strength and public exponent
oRSA.GenerateKeys(cipherStrength, pubExponent);

VB.NET

Dim cipherStrength As Integer = 2048
Dim pubExponent As Integer = 17

'Attach the KeysGenerated event handler
AddHandler oRSA.OnKeysGenerated, AddressOf KeysGenerated

'Use default values
oRSA.GenerateKeys()
'Override the cipher strength
oRSA.GenerateKeys(cipherStrength)
'Override cipher strength and public exponent
oRSA.GenerateKeys(cipherStrength, pubExponent)

 

Importing / Exporting Keys
Keys can be exported to / imported from XML.  These XML keys are compatible with .NET's RSACryptoServiceProvider, so an XML key that is exported from the RSACSP can be imported into the RSACrypto class.  As a note, I've also included an RSAParameters type.  Keys can also be exported to / imported from an RSAParameters instance.

C#

string xmlKeys;

//Export to XML.  Pass "True" to include private key data, "False" for public only.
xmlKeys = oRSA.ToXmlString(true);

//Import the keys from XML
oRSA.FromXmlString(xmlKeys);

VB.NET

Dim xmlKeys As String

'Export to XML.  Pass "True" to include private key data, "False" for public only.
xmlKeys = oRSA.ToXmlString(True)

'Import the keys from XML
oRSA.FromXmlString(xmlKeys)

 

Encrypting / Decrypting
Once your RSACrypto instance has been initialized, you can encrypt and decrypt data.  If no keys have been generated, the RSACrypto class will generate an exception.  To perform data operations you must first make a call to GenerateKeys( ) or import a key pair from XML or an RSAParameters instance.  For this example, we're assuming that you have a Textbox (Textbox1) with the data you want to be encrypted, and another Textbox (Textbox2) where you want the decrypted text to be placed.

C#

//Convert your  text to a byte array
byte[] rawBytes = System.Text.Encoding.UTF8.GetBytes(Textbox1.Text);

//Encrypt your raw bytes and return the encrypted bytes
byte[] encBytes = oRSA.Encrypt(rawBytes);

//Now decrypt your encrypted bytes
byte[] decBytes = oRSA.Decrypt(encBytes);

//Convert your decrypted bytes back to a string
Textbox2.Text = System.Text.Encoding.UTF8.GetString(decBytes, 0, decBytes.Length);

VB.NET

'Convert your text to a byte array
Dim rawBytes() As Byte = System.Text.Encoding.UTF8.GetBytes(Textbox1.Text)

'Encrypt your raw bytes and return the encrypted bytes
Dim encBytes() As Byte = oRSA.Encrypt(rawBytes)

'Now decrypt your encrypted bytes
Dim decBytes() As Byte = oRSA.Decrypt(encBytes)

'Convert your decrypted bytes back to a string
Textbox2.Text = System.Text.Encoding.UTF8.GetString(decBytes, 0, decBytes.Length)

 

Signing / Verification
Often times digital signatures are used to validate product keys and license information.  The example below demonstrates signature generation and verification using SHA256.  This example assumes you have two text boxes, Textbox1 containing the text you are signing, and Textbox2 containing the text you want to verify against the signed data.  If no key data has been loaded the RSACrypto class will generate an exception.  To perform data operations you must first make a call to GenerateKeys( ) or import a key pair from XML or an RSAParameters instance.
Note*: The private key is used to generate the signed data and the public key is used when verifying.  This example assumes you've already loaded the appropriate key data into the RSACrypto class.

C#

//Create an instance of the signature provider that will be used
RSA.SignatureProviders.EMSAPKCS1v1_5_SHA256 sigProvider;
sigProvider = new RSA.SignatureProviders.EMSAPKCS1v1_5_SHA256();

//Convert the text you want to sign into a byte array
byte[] rawBytes = System.Text.Encoding.UTF8.GetBytes(Textbox1.Text);

//Sign your data and return the signed bytes
byte[] signedBytes = oRSA.SignData(rawBytes, sigProvider);

//Convert the text you want to verify against into a byte array
byte[] compareBytes = System.Text.Encoding.UTF8.GetBytes(Textbox2.Text);

//Return a boolean value indicating whether the values matched
bool isMatch = oRSA.VerifyData(compareBytes, signedBytes, sigProvider);

VB.NET

'Create an instance of the signature provider that will be used
Dim sigProvider As New RSA.SignatureProviders.EMSAPKCS1v1_5_SHA256

'Convert the text you want to sign into a byte array
Dim rawBytes() As Byte = System.Text.Encoding.UTF8.GetBytes(Textbox1.Text)

'Sign your data and return the signed bytes
Dim signedBytes() AS Byte = oRSA.SignData(rawBytes, sigProvider)

'Convert the text you want to verify against into a byte array
Dim compareBytes() As Byte = System.Text.Encoding.UTF8.GetBytes(Textbox2.Text)

'Return a boolean value indicating whether the values matched
Dim isMatch As Boolean = oRSA.VerifyData(compareBytes, signedBytes, sigProvider)

 

Future Changes

As a note, currently none of the work is performed asynchronously (aside from some background processing performed during key generation).  I will be re-vamping the key generation process to function async to avoid blocking the user interface and freezing the browser while the keys are being generated.

Key generation is now performed Asynchronously.  In the future I will be adding additional cryptography functionality to the library that doesn't exist in Silverlight, such as support for SHA384, SHA512, 3DES, and various AES implementations.

 

Extensibility and Conclusion

I've tried to design the RSACrypto class to be somewhat extensible.  There are three primary interfaces that can be implemented to extend RSACrypto.   If you would like to add a different hashing scheme, such as MD5, you can Implement the IHashProvider interface.  If you want to create your own SignatureProvider you can implement the ISignatureProvider interface.  And if you want to create your own PaddingProvider, you can implement the IPaddingProvider interface.  The most important notes are as follows:

  1. The signature provider does not encrypt the data, however it performs both the hashing and padding prior to encryption.  Since ISignatureProvider uses IHashProvider, you can easily implement your own signature provider, however you will need to make sure you also implement proper padding.
  2. The PaddingProvider is also executed prior to encrypting the data.  Data is not encrypted inside the PaddingProvider, merely padded.

I hope someone will find this class useful.  If you have any questions or comments please feel free to comment below or contact me via the Contact page.

Sample Application

Here's a quick sample application that leverages the DH.Cryptography library for RSA encryption.  It quickly demonstrates key generation and the encryption / decryption capabilities.  It also illustrates the issue of performing intensive operations synchronously as the browser will temporarily freeze while generating keys (hence the reason for future async development for the library).