How to Use Digital Signatures in C#

Using digital signatures to build a simple licensing solution

How to Use Digital Signatures in C#

In some of my projects I had a need to create and verify licenses. I needed to be able to verify that the license was one that I had issued and had not been tampered with or modified. A digital signature was the natural way to implement this requirement. I did a bit of digging into the C# cryptographic APIs and learned that everything required to create and verify digital signatures was already provided.

I am not going to go into a detailed explanation of how digital signatures work as there are many good references available on the subject. In this article I am going to focus on one specific implementation using C#. This example also does not include features such as revoking licenses or limiting the number of installations. This is just a bare bones example to demonstrate one possible solution.

Creating a key pair

Before I can sign anything I would need to generate a public/private key pair. The private key is for creating the digital signature. As its' name implies, this key must be stored securely and not shared. The public key is for verifying that the signature is valid and that the license has not been changed. I would need to embed the public key in my software so that the license could be verified at run time.

In order to create a key pair we can use the RSACryptoServiceProvider class which is part of the System.Security.Cryptography library. This is a one time step as once we have generated the keys we only need to do it again if we require a new set of keys. The new keys won't work with any signatures we generated using a prior set of keys.

The first step in generating the keys is to instantiate the class

    RSACryptoServiceProvider rsa = new RSACryptoServiceProvider();

This initializes a new instance of the RSA class with a randomly generated key pair. Now we need to retrieve the keys and save them. There are multiple ways to accomplish this. I chose to retrieve them as XML strings as I felt they were easy to work with in that format. The way to do that is to use the ToXmlString() method of the RSA class. Passing in a parameter of true includes the private key. A parameter of false gives us just the public key.

    string privateKey = rsa.ToXmlString(true);
    string publicKey = rsa.ToXmlString(false);

Now that we have the keys we can save them however we wish. The simplest way is to write them to files on disk.

    string privateKeyPath = "private.key";
    string publicKeyPath = "public.key";

    File.WriteAllText(privateKeyPath, privateKey);
    File.WriteAllText(publicKeyPath, publicKey);

Store the private key somewhere safe.

Signing the data

Now that we have the keys we can sign our data. One implementation would be to use a license terms class to hold whatever information about the license we care about. For this example I will just include the user's name and email address. In a real implementation you would probably need to include things like issue date, expiration date, the product, the license type, or maybe what application features are enabled by this license. Those items would be easy enough to add.


    [Serializable]
    public class LicenseTerms
    {
        public String UserName { get; set; }
        public string UserEmail { get; set; }

        public override string ToString()
        {
            StringBuilder builder = new StringBuilder();
            builder.Append(" UserName: " + this.UserName);
            builder.Append(" UserEmail: " + this.UserEmail);
            return builder.ToString();
        }
    }
    

There are a couple of things worth noting. The first is that I declared this class as Serializable. The reason for this will be explained in a moment. The other is that I chose to over ride the ToString() method. This was done purely as a convenience for debugging. It becomes more useful if we later decide to add more fields to the license.

In order to create a digital signature for the LicenseTerms class we will use the SignData() method of the RSA class we instantiated earlier. The only problem is that this method signs an array of bytes, not a LicenseTerms class. So now we need a way to convert the LicenseTerms to an array of bytes. We can accomplish this using serialization. That is the reason for declaring the LicenseTerms class as serializable.

Here's the method we need to add to the LicenseTerms class. It returns the class as a byte array.

    public byte[] GetLicenseData()
    {
        using (MemoryStream ms = new MemoryStream())
        {
            // create a binary formatter:
            BinaryFormatter bnfmt = new BinaryFormatter();

            // serialize the data to the memory-stream;
            bnfmt.Serialize(ms, this);

            // return a byte array representation of the binary data:
            return ms.GetBuffer();

        }
    }

Now we have everything we need to sign the license terms. Here's the code to sign the license data. We need the private key in XML format and the license terms. We use the FromXmlString method of the RSA class to initialize it with our private key.

    RSACryptoServiceProvider rsa = new RSACryptoServiceProvider();
    rsa.FromXmlString(privateKey);

    // get the byte-array of the license terms:
    byte[] license = terms.GetLicenseData();

    // get the signature:
    byte[] signature = rsa.SignData(license, new SHA1CryptoServiceProvider());
 

Save the signature along with the license terms. One method would be to create a License class to hold the signature and the license terms. Then you can serialize the License class to a disk file. A sample License class would look something like the following class. You may choose to implement this differently depending on your requirements.

   [Serializable]
    public class License
    {
        public LicenseTerms LicenseTerms { get; set; }
        public byte[] Signature { get; set; }
    }    

Verifying the signature

At run time we want to be able to verify that the license signature is valid. The RSA class provides the method VerifyData() to perform that check. We need to pass in the data that was signed as a byte array as well as the signature, also as a byte array. Since we saved the public key as XML we can use the FromXmlString() method of the RSA class to initialize it with our public key.


    RSACryptoServiceProvider rsa = new RSACryptoServiceProvider();
    rsa.FromXmlString(PublicKey);
    
    // get the license terms
    byte[] terms = licenseTerms.GetLicenseData());

    // use the signature we generated. If you were using the License class
// you woul ddo something like this
byte[] signature = license.Signature; if (rsa.VerifyData(terms, new SHA1CryptoServiceProvider(), signature)) { Console.WriteLine("Signature is valid"); Console.WriteLine("Terms: " + license.LicenseTerms); } else { Console.WriteLine("Signature is not a valid signature"); }

That completes this example. To sum up, we saw how to generate a public/private key pair, how to digitally sign something, and how to verify the signature. I hope you find this example instructive. Have fun coding!

Written by KB3HHA on 08/15/2020