Click here to Skip to main content
Click here to Skip to main content

Neat License

, 20 Dec 2007 CPOL
Rate this:
Please Sign up or sign in to vote.
An article on how to use a code-signing key to sign other data

Introduction

In this article, I will show how to sign any data with the private key you use to sign your assemblies.

Background

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 try-catch (e.g. I don't catch base64 decoding exceptions). The code for reading the key pair from disk is a one-liner:

rsa.ImportCspBlob(System.IO.File.ReadAllBytes("key.snk"));

Then we can sign any binary data:

byte[] licenseData = Encoding.UTF8.GetBytes(licenseText);
byte[] signature = rsa.SignData(licenseData, 
    new System.Security.Cryptography.SHA1CryptoServiceProvider());

And, finally, we write the data and the signature into the license file (as text):

System.IO.File.WriteAllText("license.txt", 
    Convert.ToBase64String(licenseData)+Environment.NewLine+
    Convert.ToBase64String(signature));

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 RSACryptoServiceProvider:

// Here is a trick: the public key in assembly file has a 12-byte header 
// at the beginning.
// We strip it - and the remaining bytes can be now imported by 
// RSACryptoServiceProvider class
byte[] tmpKey = new byte[pubKey.Length - 12];
Array.Copy(pubKey, 12, tmpKey, 0, tmpKey.Length);
rsa.ImportCspBlob(tmpKey);

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[0]);
if (rsa.VerifyData(licenseData, 
    new System.Security.Cryptography.SHA1CryptoServiceProvider(), 
    Convert.FromBase64String(licenseLines[1]))) {
    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.

License

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

Share

About the Author

PTJA
Software Developer
Poland Poland
Jarek Andrzejewski is now an employee in CGI Poland (www.cgi.com) and also runs his own ISV business offering .NET tailored solutions.

Comments and Discussions

 
-- There are no messages in this forum --
| Advertise | Privacy | Terms of Use | Mobile
Web03 | 2.8.141220.1 | Last Updated 20 Dec 2007
Article Copyright 2007 by PTJA
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid