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 public key, and
only the intended recipient - who must possess the matching private
key - would be able to decrypt and use it. Private 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:
Dim str as String
= "HelloThere" Dim RSA2 As RSACryptoServiceProvider = New
RSACryptoServiceProvider(cspParam) ' ---Load the public
key--- RSA2.FromXmlString(publicKey) 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 PRIVATE
key:
Dim RSA3 As
RSACryptoServiceProvider = New
RSACryptoServiceProvider(cspParam) '---Load the PRIVATE
key--- RSA3.FromXmlString(privateKey) 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(cspParam)
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 PUBLIC
Key:<BR>") Dim str as String = "HelloThere" Dim
RSA2 As RSACryptoServiceProvider = New
RSACryptoServiceProvider(cspParam) '---Load the public
key--- RSA2.FromXmlString(publicKey) 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 PRIVATE key:<BR>") Dim
RSA3 As RSACryptoServiceProvider = New
RSACryptoServiceProvider(cspParam) '---Load the PRIVATE
key--- RSA3.FromXmlString(privateKey) 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.
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 an independent 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 currently
focuses on the .NET Platform. Peter can be reached at pbromberg@yahoo.com Pete
Says: "Post your questions and comments to our FORUMS!"
|