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.