In this article, I will show how to sign any data with the private key you use to sign your assemblies.
The general idea of digital signature using asymmetric cryptography is very simple:
- You can sign data using your private key and anyone can verify the signature using the public key (being a pair for the private one).
- No one can compute or guess the private key if he knows only the public key, so the public key can be... hmm, public.
- We have to be sure that the public key we use to verify the signature really belongs to a particular party (person or company).
In PKI (Public Key Infrastructure), we have a chain of certificates. There are some "root" CAs (Certificate Authorities) which certify the public keys of other parties and thus we can believe that code signed by "Microsoft" is really from Microsoft (we believe that root CAs wouldn't certify a key if they were not sure that it belongs to Microsoft).
So, when we want to verify a signature of data in the PKI world, we can just get the public key of the signer from the CA and use it in the asymmetric algorithm.
One of the applications of asymmetric cryptography is digital code signing. In the .NET world, we have all we need to sign our code: we can generate as many key pairs as we want (using sn.exe from the .NET Framework SDK), we can build our assemblies signing them and we can even secure our system to avoid running any single assembly without the signature of the party we trust.
I made a medical imaging database for dermatologists. They often decide not to connect their machines to any network (to protect the sensitive data from being stolen), so any kind of on-line activation was not an option. My idea is to provide an individual personalized license for every client and make tampering this license not easy.
I like the idea described in the article "Piracy and Unconventional Wisdom" by Chad Z. Hower aka Kudzu, so I wanted to make my solution as easy as possible for the client. The license file is just a separate plain text file which is easy to transport, so for example, upgrading the program to the full product requires just copying one file to the program folder. The license file is plain text, but (using Base64 encoding of any binary data) it can contain any complex license data (e.g. serialized graph of objects).
And Now the Idea: Use Code Signing Keys for Other Purposes
Some day I asked myself: can I use the same key for signing my assemblies and for signing any other data? After some research, I found a way to import keys from a *.snk file (generated by sn.exe tool) to an RSA cryptographic object from
System.Security namespace, but it required some byte array operations (it was in the .NET 1.1 "era"). In .NET 2.0, the
RSACryptoServiceProvider class fortunately "understands" *.snk files "off the box".
So now we can use our code-signing key to sign any data and we can use the public key embedded in our application assembly for verification. Simple, isn't it?
If you are interested in key BLOBs, you can read the MSDN article "Private Key BLOBs".
Using the Code
In the article code, you can find two executables:
- LicenseGenerator which generates the license (text) file by signing license data (in the demo you type some text, but in a real application, you probably serialize some object graph instead).
- Application which reads the license file, verifies the signature and displays the license text if the signature is correct.
I wanted to keep the code simple, so please note that I omitted some
catch (e.g. I don't catch base64 decoding exceptions). The code for reading the key pair from disk is a one-liner:
Then we can sign any binary data:
byte licenseData = Encoding.UTF8.GetBytes(licenseText);
byte signature = rsa.SignData(licenseData,
And, finally, we write the data and the signature into the license file (as text):
When we start our application, we need to get the public key from the assembly. We need to strip some header from it before we pass the key to
byte tmpKey = new byte[pubKey.Length - 12];
Array.Copy(pubKey, 12, tmpKey, 0, tmpKey.Length);
Then we can get data (and a signature) from the license file and verify the signature:
string licenseLines = System.IO.File.ReadAllLines("license.txt");
byte licenseData = Convert.FromBase64String(licenseLines);
Console.WriteLine("License is:\n" + Encoding.UTF8.GetString(licenseData));
If you want to quickly play with the demo, just unpack NeatLicenseDemo.zip and run rundemo.bat. You will be asked to type some text and then you'll see that verification passed.
If you want to play with the code, unpack NeatLicense.zip and run:
- GenerateKey.bat - To generate a new key pair (please edit it and change the path to sn.exe if needed).
- buildall.bat - To build executables (it calls
MSBuild, so maybe you need to change the path to it, too). The batch uses the solution (*.sln) file as a script, so you can open the project using Visual Studio, too.
- rundemo.bat - The same batch file as in the demo zip.
Points of Interest
One can ask: What if the hacker will generate a new key pair, re-sign our assembly and prepare a new license file and then distribute the program? Hmm, that's right, we can't avoid this - exactly as we can't prevent a hacker from changing our code to bypass any other software protection (on any platform, not only .NET). Can we try? I think we should focus on client needs, not on fighting with the cruel world.
But the good thing: A pirate can't hide his activity. It is impossible (to be precise: probability is extremely low) to generate a new key pair with the same public key, so we can always detect that the assembly was tampered - it's public key (and public key token) will change. To make the hacker's life harder, we can check random assembly key bytes in several places in our program. As an example, you can see the
CheckPublicKey method, but in the real code I suggest using inline checks to make tracking harder. If we use only one function, the protection could be easily deactivated by the hacker. I commented-out a call to this function, because it will fail when you generate your keys.