|
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. Pete's samples at GotDotNet.com have been downloaded over 41,000 times. You can read Peter's UnBlog Here. --><-- NOTE: Post QUESTIONS on FORUMS! |  |
|