Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / web / IIS

Using RSA Public Key Encryption in a Shared Web Hosting Environment

4.90/5 (54 votes)
19 Mar 2011CPOL10 min read 4   5.3K  
This article provides a way to use RSA public key encryption in scripts running on a Web server hosted by a shared hosting company. It also demonstrates how to use RSA in .NET to solve the 'real world' problem of signing license codes so that they cannot be forged.
Screenshot - rsa.gif

Introduction

If you are thinking of using RSA encryption on a Web server maintained by a shared hosting company (as many small websites are), you might find yourself out of luck. The RSA encryption class (RSACryptoServiceProvider) supplied by .NET is not usable in such an environment because it would compromise the security of the server's own private keys. This article provides an alternative solution that does not require the use of RSACryptoServiceProvider and hence can be used anywhere.

What is Public Key Encryption (in 15 Words or Less)?

Public key encryption provides a secure, snoop-proof means of transporting small amounts of data from one party to another without the need for any previously agreed secret key information. What one needs to achieve this is a public-private key pair. The public key is made generally available while the private key is kept secret (by the person who generated it in the first place, usually). Data encrypted with the public key can only be decrypted by the person holding a copy of the private key, while data encrypted with the private key is known to come from the holder of that key. There is some further reading here.

Our company, AlpineSoft, uses RSA public key encryption for generating software license codes that cannot be forged. When the user purchases our shareware application, he is emailed a license code which is signed with our private key. At the receiving end, the software verifies this code using our public key and only if the signature is valid is the license code accepted. This procedure is automated, but we ran into problems with the signing process on our public Web server, which runs on a shared hosting service. What that problem was, and how we overcame it, is the subject of this article.

What are Digital Signatures?

A digital signature is a way of proving that a piece of information - a license code in our case - comes from a particular source. What is actually signed is not the data itself, but a hash (also known as a message digest) of the data. A hash is a fixed length string of bytes (16 bytes for MD5 and 20 bytes for SHA1) that is calculated by a 'one-way' hashing algorithm. The idea is that every message almost certainly generates a unique hash value, and contriving a different message which generates the same hash value is effectively impossible. If you want to know more about hashing, read this.

To sign a piece of data, we do this:

  1. Compute the hash of the data to be signed
  2. Encrypt the hash using our private key
  3. Send the data and encrypted hash to the receiver

In other words, a digital signature is just an encrypted hash of the data to be signed. At the receiving end, the integrity of the signed data is verified as follows:

  1. Decrypt the digital signature using our public key
  2. Compute the hash of the data received
  3. Compare the hash computed from the data with that contained in the decrypted signature

If the values differ, the signature is not valid. The point about this procedure is that only the sender can generate a valid encrypted hash, since only the sender knows the private key.

Using Digital Signatures in .NET

RSA encryption services in .NET are provided by the RSACryptoServiceProvider class. There are also classes to perform hashing, namely MD5CryptoServiceProvider and SHA1CryptoServiceProvider. None of these classes are hard to use, once you understand the principles behind what they do.

Key Generation

The first thing you need to do is to generate your own private-public key pair. You also export this in XML form so that you can refer to it later. You only need to do this once and it can be done on any computer, not necessarily the one you are going to use for signing things. The mechanics of doing this are simple:

C#
RSACryptoServiceProvider rsacp = new RSACryptoServiceProvider (512);
string my_key_pair_formatted_as_an_xml_string = rsacp.ToXmlString (true);

Here 512 is your desired key length, in bits. Microsoft doesn't actually make it clear, but the RSACryptoServiceProvider constructor generates a random key pair for you of the length you specify. The XML string generated encapsulates the magic numbers comprising your key. It looks something like this (when nicely formatted):

XML
<RSAKeyValue>
    <Modulus>zjFmn/hT8J3wZqW5IhU4aQggHtqZmL+OpWO1HCgo4x38HAbRXrrzXH2d3FA0AOSipfluDh1vSq/
        FMC/Kvm//xw==</Modulus>
    <Exponent>AQAB</Exponent>
    <P>+yOEiADmhrquMF4vms1fo4jItF/PlziDNleyxEZLqFk=</P>
    <Q>0i8pKzRseS6ODbgFTw0pZEf9Z+SXtsyDWqk09CVH5R8=</Q>
    <DP>0hE0k5rFOV8/wv+VrFQrspwA3jfiaeiAgN08kEcIlAk=</DP>
    <DQvLrE5ZRa1TkpwVk3eKAyVeGihu9XFel9+gsJ6OA6cJSM=</DQ>
    <InverseQ>Uj3vYWWtNW346N6Tn25RmDWfNrlysdKl1qkANGx4JEI=</InverseQ>
    <D>zXQgBAoW6c0WO9Gp1TI70TxNdTDwl2lYI6hkUDgb9aChZfOswClaRV/
        ApM+QZQQmFYZbWphmMtlUrEAuMe6C4Q==</D>
</RSAKeyValue>

Don't worry if this looks intimidating - it's just a bunch of very large numbers, encoded as base64 strings so that they can be stored as text ('base64' is a convenient way of representing binary data as printable text). What you see here is a private key, i.e. it contains all the information needed to sign or decrypt messages. To verify or encrypt messages, you need only the public key which consists of just the first two fields:

XML
<RSAKeyValue>
    <Modulus>zjFmn/hT8J3wZqW5IhU4aQggHtqZmL+OpWO1HCgo4x38HAbRXrrzXH2d3FA0AOSipfluDh1vSq/
        FMC/Kvm//xw==</Modulus>
    <Exponent>AQAB</Exponent>
</RSAKeyValue>

Signing Data

To sign your license code string (or whatever) in your signing script, you must first load up an RSACryptoServiceProvider instance with the private key you made earlier, which you do like this:

C#
RSACryptoServiceProvider rsacp = new RSACryptoServiceProvider (512);
string my_private_key = "<our PRIVATE key in XML form, as generated above>";
rsacp.FromXmlString (my_private_key);

You are now ready to start signing things. To sign our license codes, we do something like this:

C#
string license_code = "Licensed to J.T. Ripper, London";
ASCIIEncoding ByteConverter = new ASCIIEncoding ();
byte [] sign_this = ByteConverter.GetBytes (license_code);
byte [] signature = rsacp.SignData (sign_this, new SHA1CryptoServiceProvider ());
string base64_string = Convert.ToBase64String (signature);

This actually does a number of things:

  1. Converts the license code string into an array of bytes, and hence suitable for hashing
  2. Calculates a 20-byte SHA1 hash of these bytes
  3. Parcels up the hash in a PKCS#1 wrapper
  4. Signs the hash by encrypting it with our private key
  5. Encodes the digital signature (which is binary) as a base64 string for transmission by email or other means

Steps 2, 3 and 4 are all carried out in the call to SignData.

Verifying Signed Data

Verifying the license code at the receiving end is, essentially, a mirror image of the above:

C#
RSACryptoServiceProvider rsacp = new RSACryptoServiceProvider (512);
string my_public_key = "<our PUBLIC key in XML form, as generated above>";
rsacp.FromXmlString (my_public_key);

string license_code = "<license code received by email>";
ASCIIEncoding ByteConverter = new ASCIIEncoding ();
byte [] verify_this = ByteConverter.GetBytes (license_code);
string base64_encoded_signature = "<base64-encoded signature generated above>";
byte [] signature = Convert.FromBase64String (base64_encoded_signature);
bool ok = rsacp.VerifyData (verify_this, new SHA1CryptoServiceProvider (), signature);
if (!ok)
     barf ();

This actually does the following:

  1. Loads up an RSACryptoServiceProvider instance with our public key
  2. Converts the license code string received by email into an array of bytes, and hence suitable for hashing
  3. Calculates a 20-byte SHA1 hash of these bytes
  4. Converts the digital signature, which was encoded as base64 for transmission, back into an array of bytes
  5. Decrypts the hash contained in the digital signature and removes the PKCS#1 wrapper
  6. Compares the decrypted hash with the one we just calculated

Steps 3, 5 and 6 are all carried out in the call to VerifyData.

And that's all there is to it; say goodbye to software piracy. Or at least, it should be, but...

But There's a Problem

Having figured out how to make our license codes secure, we thought we could sit back and relax, but when we tried to run our signing script on our public Web server (which runs in a shared hosting environment), we got the following security exception from RSACryptoServiceProvider.FromXmlString():

System.Security.SecurityException: Request for the permission of type
'System.Security.Permissions.KeyContainerPermission, mscorlib, Version=2.0.0.0,
Culture=neutral, PublicKeyToken=b77a5c561934e089' failed.

Help! What do we do?? A bit of Googling around, and a quick email to our (excellent) Web hosting providers Liquid Six, revealed that the reason for this lies deep inside the Windows crypt API, on which RSACryptoServiceProvider is based. Essentially, to allow scripts to load up their own private keys would compromise the security of the Windows key store, so all sensible Web hosting providers turn it off lest a rogue script steals / overwrites the hosting provider's own private keys. This strikes me as a major snafu in the Windows crypt API but there you go. I guess we're stuck with it.

Some more Googling turned up two essential resources: Chew Keong TAN's most excellent BigInteger class and some LGPL 'C' code to do the requisite calculations and PKCS#1 encapsulation from XySSL (originally written by Christopher Devine). These resources were particularly useful to me because (a) the ability to manipulate numbers with hundreds of digits is a specialist area, and (b) I hate ASN.1 (on which the PKCS#1 format is built). The calculations themselves are deceptively simple.

A day or two of stitching and patching later and EZRSA was born. EZRSA does pretty much everything that RSACryptoServiceProvider can do but entirely in managed code and without using the Windows crypt API. As a result, it will run anywhere, no matter what trust level your Web hosting provider imposes on you (which is what we needed).

Using the Code

Using EZRSA is a lot simpler than understanding what it does. It is pretty much plug-compatible with RSACryptoServiceProvider, so if you have written code using the latter you should find it easy enough to convert it to use EZRSA. EZRSA runs under .NET 1.1 or later (but if you want to run it under .NET 1.1 you will need to compile it manually or build it under Visual Studio .NET 2003; grrrr!).

I have included a Visual Studio 2005 project to build EZRSA as a DLL (AlpineSoft.EZRSA.dll) which you can then simply drop into the bin directory on your Web server. I have also included a copy of Chew Keong TAN's BigInteger class as the one on his page has a bug in it which is fixed in the version I provide.

If you are looking for a pre-built DLL (which is built for .NET 2.0), you will find it in with the demo program (see link at the top of the article). This program demonstrates the fundamentals and also shows you how to use RSA to encrypt and decrypt data, if that is your bag. A word of warning though; don't use RSA (in any form) to encrypt large messages. It is not designed to do this and is very slow. Instead, use your public RSA key to encrypt a DES, Blowfish or whatever symmetric key and send that to the receiver first. You can then safely use this symmetric key to encrypt the body of your message using the encryption method of your choice, as only the receiver can decipher the symmetric key. I am unaware of any 'trust level' issues doing this in a shared hosting environment.

Points of Interest

The thing that struck me most strongly doing this project is what a nice language C# is to program in. The code I converted from C ended up much shorter and easier to read by the time I was done. This left some time to add some bells and whistles (in the form of RSACryptoServiceProvider compatibility) that the original obviously lacked. I also found it interesting that RSA is based in part on the work of those two cheeky rascals Pierre Fermat (1601 - 1665) and Leonhard Euler (1707 - 1783), both of whom made groundbreaking advances in mathematics and, in particular, number theory.

And finally, a word about keeping your private key(s) safe. The major problem with the foregoing is that your private key appears in your server-side scripts (although it's never sent to the browser of course). It is this limitation which prompted Microsoft to lock up private keys in Key Containers in the crypt API. UNFORTUNATELY, they made this the only way to use them, which was a mistake IMO. Anyway, from a .NET standpoint, the obvious way to make things a little more secure is to compile your private key into a DLL, perhaps wrapped up in a simple API to (say) sign a string. This way, if someone hacks into your Web server, at least they won't get a plaintext copy of your private key.

Oh yes, one more thing. It is now apparently possible to crack a 640 bit RSA key in under a year if you put your mind to it. Shows how much faster computers have become. When RSA was designed back in 1977, the estimate was over 1 billion years.

History

  • October 2007: Initial version
  • March 2009: Updated to work correctly when signing XML documents with the SignedXml class. Please note however that SignedXml::CheckSignature will always fail when used with EZRSA because SignedXml::CheckSignature checks internally that the RSA object passed in derives from RSACryptoServiceProvider which EZRSA does not (and cannot) do. Sorry about that, but this is Microsoft's bug, not mine, and there's nothing I can do about it. Use RSACrytoServiceProvider for checking XML signatures instead (if you can) or you might be able to refer to the Microsoft .NET sources (which have now been made public) and write your own CheckSignature function based on what you find there.
    Thanks to Wout de Zeeuw for his assistance with this (he did most of the work, actually).
  • June 2009: Fixed two significant bugs, both in Encrypt(), as reported by xxyyz (aka KurtA). Many thanks to him for providing both the bug report and the solution! See his posting below for details.
  • March 2011: Support has been added for SHA-256 hashing. Thanks to Pierre Arnaud for this contribution.

http://www.alpinesoft.co.uk

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)