Mad about .NET A blog from Jose Fco Bonnin


One of the main problems people faces when they try to encrypt / decrypt with .NET classes is that, after decrypt, the resulting string contains unexpected chars or "garbage" at the end.

Normally, you will think that there is a problem with the padding used by the algorithm. But even if it’s related, the problem comes out because of a common people does when using the CryptoStream class.

The next code reproduces the error.

   1: static void Main(string[] args)
   2: {
   3:     RijndaelManaged rijndael = new RijndaelManaged();
   4:  
   5:     ICryptoTransform encryptor = rijndael.CreateEncryptor();
   6:     ICryptoTransform decryptor = rijndael.CreateDecryptor();
   7:  
   8:     string original = "jose fco bonnin";
   9:     System.Console.WriteLine("Encrypted: " + original + "|");
  10:  
  11:     byte[] plainData = System.Text.Encoding.Default.GetBytes(original);
  12:     byte[] encryptedData;
  13:  
  14:  
  15:     using (MemoryStream memoryStream = new MemoryStream())
  16:     {
  17:         using (CryptoStream cryptoStream = new CryptoStream(memoryStream, encryptor, CryptoStreamMode.Write))
  18:         {
  19:             cryptoStream.Write(plainData, 0, plainData.Length);
  20:             cryptoStream.FlushFinalBlock();
  21:         }
  22:  
  23:         encryptedData = memoryStream.ToArray();
  24:     }
  25:  
  26:     byte[] decrypted;
  27:     using (MemoryStream memoryStream = new MemoryStream(encryptedData))
  28:     {
  29:         using (CryptoStream cryptoStream = new CryptoStream(memoryStream, decryptor, CryptoStreamMode.Read))
  30:         {
  31:             int length = encryptedData.Length;
  32:             decrypted = new byte[length];
  33:  
  34:             cryptoStream.Read(decrypted, 0, length);
  35:         }
  36:     }
  37:  
  38:     System.Console.WriteLine("Decrypted: " + System.Text.Encoding.Default.GetString(decrypted) + "|");
  39:  
  40:     Console.ReadKey();
  41: }

If you execute the code, you will see how the original string and the decrypted string are different. I've added the pipe char ("|") at the end of the string to see it better.

The problem with he code above is that you create an array with the length of the encrypted data, which not necessarily will have the same size as the decrypted byte array. The code below shows you how you can implement the decryption properly. You could do it also by intializing the MemoryStream with the decrypted data and then read from the cryptoStream using a StreamReader.

   1: using (MemoryStream memoryStream = new MemoryStream())
   2: {
   3:     using (CryptoStream cryptoStream = new CryptoStream(memoryStream, decryptor, CryptoStreamMode.Write))
   4:     {
   5:         cryptoStream.Write(encryptedData, 0, encryptedData.Length);
   6:  
   7:         cryptoStream.FlushFinalBlock();
   8:     }
   9:  
  10:     decrypted = memoryStream.ToArray();
  11: }

As you can see, we do the same as to encrypt the string. We create the CryptoStream using the CryptoStreamMode.Write instead of CryptoStreamMode.Read and we write to it, the only difference is that, of course, we use the decryptor instead of the encryptor.

If you do the changes to the first code you will find that both strings are now exactly the same.





.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));