RSA Encryption in .NET -- Demystified!

By Peter A. Bromberg, Ph.D.
Printer - Friendly Version

Peter Bromberg

When I first started studying the CryptoGraphic classes in the .NET Platform, I had some difficulty understanding the documentation. At first I just felt a little embarrassed about my difficulties (after all, I've got a postgraduate degree and I'm a Mensa guy). But then as I continued on, I began to realize it wasn't so much me at all - fact is, the MS documentation in this particular area is simply obtuse! I don't know what they were thinking when they wrote the docs for the .NET Cryptographic Provider classes, but they sure weren't targeting it at newbies to encryption!



After a little study, I found it pretty easy to use the RC2, DES and other Symmetric encryption classes (see my article here on XML-safe encryption). Still, the RSA Asymmetric classes with all the CSPParameters and Public and Private key stuff just threw me for a loop! And it seems, no matter where I searched, nobody had written any good examples of how to use them.

Undaunted, I studied on and I think I've finally arrived at the point of being able to comfortably use this stuff and write meaningful working code with it. Not only that, but I believe I've also internalized a pretty good way to explain it all without sounding like I'm regurgitating that awful MS documentation (don't get me wrong, most of MS's documentation is first-rate).

As you probably already know, symmetric cryptographic algorithms use a single "key" for encryption / decryption. You either know the key or you don't. Because of this they are faster and more suited to one-pass encryption or decryption of larger amounts of data.

However getting that single key to a user in a secure way may be very difficult. With Public Key encryption, we generate a Public Key and a corresponding Private key. The way the algorithms work is that the private key can only be used to decrypt information that has been encrypted using its matching public key. Conversely, the public key can only decrypt information encrypted with the private key. Asymmetric encryption/ decryption is notably slower than symmetric and therefore is more suited for small amounts of data. Now why would this be of value?

Typically, a system that uses public key encryption would make its public key freely available. They would, however, guard the corresponding private key very carefully. Users could therefore send private data encrypted with the private key, and only the intended recipient - who must possess the matching public key - would be able to decrypt and use it. Public keys are also used to verify that a message is from the actual sender, because the sender is the only one who has the private key. That's essentially how XMLSignature works.

So for example, a system, which makes its public key freely available (as with a WebMethod) might receive a user's message containing login information that is encrypted using the public key. It would then use its private key to decrypt this information, compare the user and password against its database, and once authenticated, use the user's decrypted password ( or some similar combination) as the "KEY" to symmetrically encrypt its response to the user. Since the user is the only one who knows her own password, she can decrypt the response securely.

The "KEY" to RSA Encryption

Whenever you create a new default constructor instance of the RSACryptoServiceProviderclass, it automatically creates a new set of public / private key information, ready to use. However, if you want to re-use previously created keys, you can do this by initializing the class with a populated CspParameters object.

For example [VB.NET]:

Dim cspParam as CspParameters = new CspParameters()
cspParam.Flags = CspProviderFlags.UseMachineKeyStore
Dim RSA As System.Security.Cryptography.RSACryptoServiceProvider
           = New System.Security.Cryptography.RSACryptoServiceProvider(cspParam)

The key information from the cspParam object above can be saved via:

Dim publicKey as String = RSA.ToXmlString(False) ' gets the public key
Dim privateKey as String = RSA.ToXmlString(True) ' gets the private key

The above methods enable you to convert the public and / or private keys to Xml Strings. And of course, as you would guess, there is a corresponding FromXmlString method to get them back. So to encrypt some data with the Public key. The no-parameter constructor is used as we are loading our keys from XML and do not need to create a new cspParams object:

Dim str as String = "HelloThere"
Dim RSA2 As RSACryptoServiceProvider = New RSACryptoServiceProvider()
' ---Load the private key---
RSA2.FromXmlString(privateKey)
Dim EncryptedStrAsByt() As Byte =RSA2.Encrypt(System.Text.Encoding.Unicode.GetBytes(str),False)
Dim EncryptedStrAsString = System.Text.Encoding.Unicode.GetString(EncryptedStrAsByt)

and as a "proof of concept", to DECRYPT the same data, but now using the Public key:

Dim RSA3 As RSACryptoServiceProvider = New RSACryptoServiceProvider(cspParam)
'---Load the Public key---
RSA3.FromXmlString(publicKey)
Dim DecryptedStrAsByt() As Byte =RSA3.Decrypt(System.Text.Encoding.Unicode.GetBytes(EncryptedStrAsString), False)
Dim DecryptedStrAsString = System.Text.Encoding.Unicode.GetString(DecryptedStrAsByt)

Now here is an example of an ASPX page that "pulls it all together", and caches the cspParam object in Application state:

<%@ Import Namespace="System.Security.Cryptography" %>
<script language="VB" runat="server">
Sub Page_Load(sender as Object, e as EventArgs)

Dim cspParam as CspParameters
If TypeOf(Application("cspparam")) is CspParameters then
Response.Write("Application Variable: " & Application("cspparam").ToString & "<BR>")
cspParam =CType(Application("cspParam"), CspParameters)
else
' Note: When you are using the application from within ASP.NET, you are not an
'interactive user. Windows needs to use the key container from the user's
' profile for RSA service provider. The profile is not loaded for
' non-interactive users, so you need to use the information stored on the
' local(machine)'s keystore with the cspParam flag as shown in the next 3 lines.
' You then pass the initialized CspParameters object in the contructor to the RSACryptoServiceProvider

cspParam= New CspParameters()
cspParam.Flags = CspProviderFlags.UseMachineKeyStore
' store in Application state so we only need to create this once
Application("cspparam")=cspParam
End if
 

Dim RSA As System.Security.Cryptography.RSACryptoServiceProvider = New System.Security.Cryptography.RSACryptoServiceProvider()

Dim publicKey as String = RSA.ToXmlString(False) ' gets the public key
Dim privateKey as String = RSA.ToXmlString(True) ' gets the private key
Response.Write("<Textarea rows=10 cols=100>PUBLIC: " & publicKey & "</TextArea>")
Response.Write("<Textarea rows=10 cols=100>PRIVATE: " & privateKey & "</Textarea>")
Response.Write("<BR>Encrypting the string ""HelloThere"" with the private Key:<BR>")
Dim str as String = "HelloThere"
Dim RSA2 As RSACryptoServiceProvider = New RSACryptoServiceProvider(cspParam)
'---Load the private key---
RSA2.FromXmlString(privateKey)
Dim EncryptedStrAsByt() As Byte =RSA2.Encrypt(System.Text.Encoding.Unicode.GetBytes(str), False)
Dim EncryptedStrAsString = System.Text.Encoding.Unicode.GetString(EncryptedStrAsByt)
Response.Write( "<Textarea rows=10 cols=100>Encrypted String: " & EncryptedStrAsString & "</Textarea>")
Response.Write("<BR>Decrypting the Encrypted String with the Public key:<BR>")
Dim RSA3 As RSACryptoServiceProvider = New RSACryptoServiceProvider(cspParam)
'---Load the Public key---
RSA3.FromXmlString(publicKey)
Dim DecryptedStrAsByt() As Byte =RSA3.Decrypt(System.Text.Encoding.Unicode.GetBytes(EncryptedStrAsString), False)
Dim DecryptedStrAsString = System.Text.Encoding.Unicode.GetString(DecryptedStrAsByt)
Response.Write( "<Textarea rows=10 cols=100>Decrypted String: " & DecryptedStrAsString & "</Textarea>")
End Sub
</script>

You can see that I test for the Application variable holding the CspParameters and, if necessary, initialize one and store it in the Application variable. In this manner we always have the same public / private key pair, and we only need to create the keys once. Of course, you could go one step further and save the ToXmlString values on the hard drive, then simply reload them each time. And here is a link to a working live version of the above page so you can try it out: Also, I'd lke to thank Shawn Steele of Microsoft who suggested changing my original choice of Encoding.Default.GetBytes/String to Encoding.UTF8.GetBytes/String (or Unicode, which is my choice here). The reason is that Encoding.Default provides the windows default code page behavior for your machine (so it can be different if you run it on different machines), and also it maps some characters to their “best-fit” counterparts if they don’t exist in that code page. For example, É and È could both be best fit to E in some code pages. That probably isn’t good behavior for encryption. UTF8 (or Unicode) provides mappings for all characters, so the best fit issue isn’t a problem. Explicitly stating the code page prevents non-ASCII character gibberish if its decoded on another machine with a different system locale.

N.B. Only one reader ever noticed that the original version of this article had the keys reversed - proving that it is possible to encrypt with either key, as long as one decrypts using the other!

Really, That's about 90% of all you need to know about RSA encryption. Sure, there are finer details to learn along the way, but if you can understand everything in this article you've pretty much got it covered. I only wish there was somebody out there that had an article like this I could read when I needed it!   And so I leave you with this little gem:

"If you want creative workers, give them enough time to play."
--John Cleese


Peter Bromberg is a C# MVP, MCP, and .NET consultant who has worked in the banking and financial industry for 20 years. He has architected and developed web - based corporate distributed application solutions since 1995, and focuses exclusively on the .NET Platform.