Saturday, October 27, 2012

Transferring Data Securely - Part 1 - Digital Signatures

It has been a while since my last post... in part b/c I have been so busy and in part b/c I really didn't have a good topic.  Recently I had to build an encryption library for work and I decided that there weren't any good posts showing how to securely transfer data using cryptography in .Net - so, I have decided to write a multi-part post that will cover how to sign a message, encrypt it using Symmetric cryptography (with a random key), and then Asymmetrically encrypt the random key.  I am not going to get into a whole lot of theoretical detail about Asymmetric cryptography, digital signatures, or Symmetric cryptography and why this is important - there are many great sources out there for this.  One thing I want to point out up front... I am not handling exceptions the way I would for a robust library.  I have not included exception handling so that I could focus on the code you need to understand - I have, however, tried to add comments in places where you might consider adding exception handling code.  Remember for all of the code snippets below to adjust the namespaces accordingly to match your workspace.

Ok, let's get started... step 1 will be to create a test certificate that you can use for testing all of this out.  Open the "Developer Command Prompt" which is located in the Visual Studios program folder.  If you can't find that, google makecert.exe and pvk2pfx.exe to find out where they are located and then copy them to a location where you can save certificates.  Once you have the command prompt open and have access to makecert and pvk2pfx, issue the following commands.  You can adjust the dates as desired, but keep the names (or adjust the name in the test code we will write below).

makecert.exe -sv TestCertificate.pvk -n "CN=TestCertificate" 
     TestCertificate.cer -sky Exchange -pe 
     -b 10/27/2012 -e 10/27/2020 -r

pvk2pfx.exe -pvk TestCertificate.pvk -spc TestCertificate.cer 
     -pfx TestCertificate.pfx
After executing the makecert command, a window will pop up asking for a password... you can chose to use one or just select none.  If you do select a password, remember it... you will need it to install the cert.  After executing these commands, you should have 3 files: TestCertificate.pfx, TestCertificate.pvk, and TestCertificate.cer.  Double click on TestCertificate.pfx and follow the instructions to install it.  If you added a password when running makecert, enter it when it tells you to.  You can keep all the default options, but I tend to select the option to "make the key exportable" - that is up to you.  Once your certificate is installed, it is time to add some code.

Next up, let's add a utility that we will use for other cryptographic work... I have created a folder named "encryption" where my classes will go... it is up to you how you want to organize your code.  Add a new class file called CryptoUtilities.cs and add the following code:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Threading.Tasks;

namespace BlogSandbox.encryption
{
    public class CryptoUtilities
    {
        // make sure certName passed in starts with 'CN='
        public static X509Certificate2 GetCertFromStore(
                                    StoreName storeName, 
                                    string certName)
        {
            X509Store store = new X509Store(storeName);
            store.Open(OpenFlags.ReadOnly);

            X509Certificate2Collection currentValidCerts =
                store.Certificates.Find(
                    X509FindType.FindByTimeValid, 
                    DateTime.Now, 
                    false);

            X509Certificate2Collection certs = 
                currentValidCerts.Find(
                    X509FindType.FindBySubjectDistinguishedName, 
                    certName, 
                    false);

            // check certs count and throw error if 0 if desired
            return certs[0];
        }
    }
}
Adjust the namespace as needed.  I have a couple recommendations here.  In this code, I am first finding all the valid certs that have not expired and then I am finding the certificate with "certName".  Since I am using "FindBySubjectDistinguishedName", the certName must start with "CN=".  I recommend that you add some code to check certName to see if it starts with CN= and if not, add it.  Also, I recommend that you check certs to make sure it has one (if you don't you will get an IndexOutOfRangeException - I think it is cleaner to instead throw a CryptographicException with a message indicating the cert could not be found, but it is up to you).  Ok, now that we have the ability to find the certificate, let's write the digital signature code.

Create a new class called DigitilSignature.cs and add the following code to it:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Threading.Tasks;

namespace BlogSandbox.encryption
{
    class DigitalSignature
    {
        private const string DefaultAlgName = "sha1";

        public static byte[] SignMessage(
                                StoreName storeName,
                                string certName,
                                string algName,
                                byte[] message)
        {
            X509Certificate2 x509Cert = 
                CryptoUtilities.GetCertFromStore(
                                            storeName, 
                                            certName);
            RSACryptoServiceProvider digSigProvider = 
                x509Cert.PrivateKey as RSACryptoServiceProvider;

            // if no provider and no private key 
            // check for and throw exception
            return digSigProvider.SignData(
                message, 
                CryptoConfig.MapNameToOID(algName));
        }


        public static byte[] SignMessage(
                                StoreName storeName,
                                string certName,
                                byte[] message)
        {
            return SignMessage(storeName,
                               certName,
                               DefaultAlgName,
                               message);
        }

        public static bool VerifySignature(
                        StoreName storeName,
                        string certName,
                        string algName,
                        byte[] message,
                        byte[] signature)
        {
            X509Certificate2 x509Cert =
                CryptoUtilities.GetCertFromStore(
                                            storeName,
                                            certName);
            RSACryptoServiceProvider digSigProvider =
                x509Cert.PrivateKey as RSACryptoServiceProvider;

            // if no provider and no private key 
            // check for and throw exception
            return digSigProvider.VerifyData(
                message,
                CryptoConfig.MapNameToOID(algName),
                signature);
        }

        public static bool VerifySignature(
                        StoreName storeName,
                        string certName,
                        byte[] message,
                        byte[] signature)
        {
            return VerifySignature(storeName,
                               certName,
                               DefaultAlgName,
                               message,
                               signature);
        }
    }
}
Let's walk through these methods starting with SignMessage.  The first thing you will notice is that I there are 2 methods, one that takes in "algName" and one that does not (but instead uses a default "sha1").  This is to provide support for Cryptographic Agility... I will be adding another post to go over this in the future.  For now, let's just use "sha1".  As an aside, the certificate we created will only support "sha1" and I need a whole post just to go over creating a cert that you can use for sha2 algorithms.  Another thing you will probably notice is that there is some shared code in the sign/verify methods, feel free to abstract at will.  One thing I would like to mention is the method call CryptoConfig.MapNameToOID(algName) - this is for Cryptographic Agility and it internally maps the algorithm name "sha1" to an object identifier that the system understands as a SHA1 algorithm.  In a future post, I will try to expand on this some more and show you how you can map different names and make it possible to use very generic names (like "HashAlgorithm") that you can map to your preferred algorithms (particularly important for government work where you must use SHA2 based algorithms).  The only real difference between the sign and verify methods is that the sign method takes a message and an algorithm and then returns the signature as a byte[] while the verify takes the message, an algorithm, and the signature we obtained in the sign method and returns a true or false indicating if the signature is valid.

Ok, the final code for this post is a simple method to execute a test on this code.

using BlogSandbox.command;
using BlogSandbox.encryption;
using System;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Threading;

namespace BlogSandbox
{
    class Program
    {
        static void Main(string[] args)
        {
            string textToSign = "Sign this text";
            byte[] signature = DigitalSignature.SignMessage(
                        StoreName.My,
                        "CN=TestCertificate", 
                        Encoding.UTF8.GetBytes(textToSign));
            bool verified = DigitalSignature.VerifySignature(
                        StoreName.My,
                        "CN=TestCertificate", 
                        Encoding.UTF8.GetBytes(textToSign),
                        signature);
            Console.WriteLine("Verified? " + verified);
            Thread.Sleep(20000);
        }
    }
}
That's it... you now have a small library that you can use to sign data.  To make the library more robust, I recommend adding methods to support using "stream" based messages so that you can handle files directly.  Additionally, you will probably want to handle exceptional cases, which I have not done here for brevity and clarity.

In the next few blog posts (I will try to wrap them up over the next few weeks) I will show you how to handle the symmetric encryption of your message, followed by the asymmetric encryption of the keys.  Once I have done that, I will go over how to put it all together to create a single message that you can send across the network/intranet/internet.

2 comments:

  1. For which type of application should your piece of code work? Would it be also valid for such things as banking transactions?
    electronic signatures

    ReplyDelete
    Replies
    1. This particular post only discusses Digital Signatures for signing a document, email, etc. I am unfamiliar with the rules/regulations surrounding banking transactions, but encrypting the data and using signatures to validate the source probably could be used for such... however, I am not an expert in this domain and could be completely wrong on that.

      Delete

Followers