Mad about .NET A blog from Jose Fco Bonnin


Recently, I recorded a Spanish video about how to encrypt/sign information using certificates and I thought it would be nice to write also a post about it. The thing is that when I tried to post it I received an alert about having a post with the same title, I already wrote this post more than two years ago :(, so while I try to figure out why I have this memory leak in my brain I will write only the missed part: how sign data using X509 certificates.

Since we already have in the previous post the code to load certificates we will focus on two methods Sign and VerifyHash.

   1:  public static byte[] Sign(byte[] hash, 
   2:      X509Certificate2 certificate)
   3:  {
   4:      if (hash == null)
   5:      {
   6:          throw new ArgumentNullException("hash");
   7:      }
   8:      if (certificate == null)
   9:      {
  10:          throw new ArgumentNullException("certificate");
  11:      }
  12:   
  13:      using (RSACryptoServiceProvider provider = 
  14:          new RSACryptoServiceProvider())
  15:      { 
  16:          provider.FromXmlString(
  17:              certificate.PrivateKey.ToXmlString(true));
  18:          return provider.SignHash(
  19:              hash,
  20:              CryptoConfig.MapNameToOID("SHA1"));
  21:      }  
  22:  }
  23:   
  24:  public static bool VerifyHash(byte[] hash, byte[] signature, 
  25:      X509Certificate2 certificate)
  26:  {
  27:      if (hash == null)
  28:      {
  29:          throw new ArgumentNullException("hash");
  30:      }
  31:      if (signature == null)
  32:      {
  33:          throw new ArgumentNullException("signature");
  34:      }
  35:      if (certificate == null)
  36:      {
  37:          throw new ArgumentNullException("certificate");
  38:      }
  39:   
  40:      using (RSACryptoServiceProvider provider = 
  41:          new RSACryptoServiceProvider())
  42:      {
  43:          provider.FromXmlString(
  44:              certificate.PublicKey.Key.ToXmlString(false));
  45:          return provider.VerifyHash(
  46:              hash, 
  47:              CryptoConfig.MapNameToOID("SHA1"), 
  48:              signature);
  49:      }
  50:  }

The idea behind these two methods is the next:

We have one party A who needs to send a message to B. B wants to be sure that the messaged received has not been modified by a third party C. To do that, A will create a digital signature that will be sent together with the message. To generate the signature A needs to compute a hash of the message, this hash is then used with method "Sign". Once A has the signature he can transmit the message and the signature to B.

When B receives the message and the signature, B needs to verify the hash. So, B computes again a hash of the received message and uses the method VerifyHash, which will return true if the message has not been modified. The next code shows you an example on how to call both methods.

   1:  static void Main(string[] args)
   2:  {
   3:      string certificateName = "Test";
   4:   
   5:      X509Certificate2 cert = LoadCertificate(
   6:          StoreName.My, 
   7:          StoreLocation.LocalMachine, 
   8:          certificateName);  
   9:      
  10:      byte[] messageSent = 
  11:          UTF8Encoding.UTF8.GetBytes("Message sent from A to B");
  12:   
  13:      // A generates the signature before send to B
  14:      SHA1Managed sha1 = new SHA1Managed();
  15:      byte[] hashA = sha1.ComputeHash(messageSent);
  16:      byte[] signature = Sign(hashA, cert);
  17:   
  18:      // B receives the message and the digital signature
  19:      byte[] messageReceived = 
  20:          UTF8Encoding.UTF8.GetBytes("Message sent from A to B");
  21:      //byte[] messageReceived = 
  22:      //  UTF8Encoding.UTF8.GetBytes("Message hacked by C");
  23:   
  24:      byte[] hashB = sha1.ComputeHash(messageReceived);
  25:   
  26:      if (VerifyHash(hashB, signature, cert))
  27:      {
  28:          Console.WriteLine("Verified");
  29:      }
  30:      else
  31:      {
  32:          Console.WriteLine("Not verified");
  33:      }
  34:   
  35:      Console.ReadKey();
  36:  }

This piece of code using the rest of methods mentioned shows you how we can verify a message has not been altered during the transmission. If you comment lines 19,20 and uncomment 21,22 you will see that after message has been altered the hash cannot be validated anymore. These short methods are one the basis of security when we try to guarantee information is not being altered from origin to destine. We have used a very basic message, but imagine you have an online shop and you don“t want people can alter from the message the number of items that will be delivered after you have authorized a payment.

Checking similar methods like SignData and VerifyData will be left as an exercise for the reader.





.NET provides us with several algorithm implementations to perform encryption and decryption. Next you can see an easy way to make use of a certificate to encrypt / decrypt  using the RSACryptoServiceProvider

The first thing we need to do is to create a test certificate, this can be done by using the tool makecert. Just open the VS 2005 command prompt and type the next line:

makecert -n CN=Test -pe -ss My -sr LocalMachine

The command above will create a certificate called "Test" and will install it in the "My" location (displayed as "Personal") of the Local Computer, the -pe will mark the private key as exportable.

You can see the test certificate you have created following the next steps:

1. Run the Microsoft Management Console (MMC) snap-in utility by running the command mmc from a command prompt.
2. On the File menu, click Add/Remove Snap-in.
3. In the dialog box that displays, click Add.
4. Under Snap-in, double-click Certificates.
5. Click Computer account to access the LocalMachine store, and then click Next.
6. Click Finish.
7. Click the Certificates node, Personal, Certificates and et voila, your certificate is there.

Now let's see how we can make use of the certificate.

   1: public static X509Certificate2 LoadCertificate(StoreName storeName, 
   2:         StoreLocation storeLocation, string certificateName)
   3: {
   4:     X509Store store = new X509Store(storeName, storeLocation);
   5:  
   6:     try
   7:     {
   8:         store.Open(OpenFlags.ReadOnly);
   9:  
  10:         X509Certificate2Collection certificateCollection =
  11:             store.Certificates.Find(X509FindType.FindBySubjectName, 
  12:                                     certificateName, false);
  13:  
  14:         if (certificateCollection.Count > 0)
  15:         {
  16:             //  We ignore if there is more than one matching cert, 
  17:             //  we just return the first one.
  18:             return certificateCollection[0];
  19:         }
  20:         else
  21:         {
  22:             throw new ArgumentException("Certificate not found");
  23:         }
  24:     }
  25:     finally
  26:     {
  27:         store.Close();
  28:     }
  29: }
  30:  
  31: public static byte[] Encrypt(byte[] plainData, bool fOAEP, 
  32:                              X509Certificate2 certificate)
  33: {
  34:     if (plainData == null)
  35:     {
  36:         throw new ArgumentNullException("plainData");
  37:     }
  38:     if (certificate == null)
  39:     {
  40:         throw new ArgumentNullException("certificate");
  41:     }
  42:  
  43:     using (RSACryptoServiceProvider provider = new RSACryptoServiceProvider())
  44:     {
  45:         // Note that we use the public key to encrypt
  46:         provider.FromXmlString(GetPublicKey(certificate));
  47:  
  48:         return provider.Encrypt(plainData, fOAEP);
  49:     }
  50: }
  51:  
  52: public static byte[] Decrypt(byte[] encryptedData, bool fOAEP, 
  53:                              X509Certificate2 certificate)
  54: {
  55:     if (encryptedData == null)
  56:     {
  57:         throw new ArgumentNullException("encryptedData");
  58:     }
  59:     if (certificate == null)
  60:     {
  61:         throw new ArgumentNullException("certificate");
  62:     }
  63:  
  64:     using (RSACryptoServiceProvider provider = new RSACryptoServiceProvider())
  65:     {
  66:         // Note that we use the private key to decrypt
  67:         provider.FromXmlString(GetXmlKeyPair(certificate));
  68:  
  69:         return provider.Decrypt(encryptedData, fOAEP);
  70:     }
  71: }
  72:  
  73: public static string GetPublicKey(X509Certificate2 certificate)
  74: {
  75:     if (certificate == null)
  76:     {
  77:         throw new ArgumentNullException("certificate");
  78:     }
  79:  
  80:     return certificate.PublicKey.Key.ToXmlString(false);
  81: }
  82:  
  83: public static string GetXmlKeyPair(X509Certificate2 certificate)
  84: {
  85:     if (certificate == null)
  86:     {
  87:         throw new ArgumentNullException("certificate");
  88:     }
  89:  
  90:     if (!certificate.HasPrivateKey)
  91:     {
  92:         throw new ArgumentException("certificate does not have a private key");
  93:     }
  94:     else
  95:     {
  96:         return certificate.PrivateKey.ToXmlString(true);
  97:     }
  98: }

The code to use the above methods is very simple:

   1: X509Certificate2 cert = LoadCertificate(
   2:      System.Security.Cryptography.X509Certificates.StoreName.My,
   3:      System.Security.Cryptography.X509Certificates.StoreLocation.LocalMachine, "Test");
   4:  
   5: byte[] encoded = System.Text.UTF8Encoding.UTF8.GetBytes("Encrypt me");
   6:  
   7: byte[] encrypted = Encrypt(encoded, true, cert);
   8: byte[] decrypted = Decrypt(encrypted, true, cert);
   9:  
  10: System.Console.Out.WriteLine(System.Text.UTF8Encoding.UTF8.GetString(decrypted));