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!