Encrypting and Decrypting Data in WinRT

As part of working on WinRT database I wanted to support ability to encrypt the data. The reason for that is that the data you persist using file system can be opened essentially in NotePad as long as you know where it is stored on the hard drive, which you can figure out. So, similarly to how I support encryption in Silverlight, I wanted to do the same in WinRT. I did some digging, and did find bits and pieces of information on the Win RT forum and in samples. Nothing however fully explained and had examples for encrypting and decrypting strings, which is what I wanted to accomplish. As a result, I wrote a helper class that I wanted to share.

My requirement were simple: just have two methods in a static class that take two parameters: input string and a password to use. I do not exactly use the password, but I use it to create a key: If you try to hack the code and pass password that was not used to encrypt into decryption method, you will get an exception. One thing that I found extremely annoying, is that all exceptions are coming in as COM exception (oh, no, do not take me back into the 90s – smile)

I added commented everywhere to make clear what I am doing. I coded this class using AES encryption provider. There is an ability in SymmetricKeyAlgorithmProvider.OpenAlgorithm to use various algorithms, but I could not find the list to save my life. As a result, I used the one mentioned in an example on the web – AES_CBC_PKCS7.

using System;
using System.Text;
using Windows.Security.Cryptography;
using Windows.Security.Cryptography.Core;
using Windows.Storage.Streams;
namespace EncryptDecrypt
{
/// <summary>
/// Class used for encryption and decryption using AES algorithm
/// </summary>
public static class EncryptionProvider
  {
    /// <summary>
    /// Encrypt a string
    /// </summary>
    /// <param name="input">String to encrypt</param>
    /// <param name="password">Password to use for encryption</param>
    /// <returns>Encrypted string</returns>
    public static string Encrypt(string input, string password)
    {
      //make sure we have data to work with
      if (string.IsNullOrEmpty(input))
        throw new ArgumentException("input cannot be empty");
      if (string.IsNullOrEmpty(password))
        throw new ArgumentException("password cannot be empty");
      // get IV, key and encrypt
      var iv = CreateInitializationVector(password);
      var key = CreateKey(password);
      var encryptedBuffer = CryptographicEngine.Encrypt(
        key, CryptographicBuffer.ConvertStringToBinary(input, BinaryStringEncoding.Utf8), iv);
      return CryptographicBuffer.EncodeToBase64String(encryptedBuffer);
    }
    /// <summary>
    /// Decrypt a string previously ecnrypted with Encrypt method and the same password
    /// </summary>
    /// <param name="input">String to decrypt</param>
    /// <param name="password">Password to use for decryption</param>
    /// <returns>Decrypted string</returns>
    public static string Decrypt(string input, string password)
    {
       //make sure we have data to work with
      if (string.IsNullOrEmpty(input))
         throw new ArgumentException("input cannot be empty");
       if (string.IsNullOrEmpty(password))
         throw new ArgumentException("password cannot be empty");
       // get IV, key and decrypt
       var iv = CreateInitializationVector(password);
       var key = CreateKey(password);
       var decryptedBuffer = CryptographicEngine.Decrypt(
         key, CryptographicBuffer.DecodeFromBase64String(input), iv);
       return CryptographicBuffer.ConvertBinaryToString(
        BinaryStringEncoding.Utf8, decryptedBuffer);
    }
    /// <summary>
    /// Create initialization vector IV
    /// </summary>
    /// <param name="password">Password is used for random vector generation</param>
    /// <returns>Vector</returns>
    private static IBuffer CreateInitializationVector(string password)
    {
      var provider = SymmetricKeyAlgorithmProvider.OpenAlgorithm("AES_CBC_PKCS7");
      var newPassword = password;
      // make sure we satify minimum length requirements
      while (newPassword.Length < provider.SupportedKeyLengths.Min)
      {
        newPassword = newPassword + password;
      }
      //create vecotr
      var iv = CryptographicBuffer.CreateFromByteArray(
        UTF8Encoding.UTF8.GetBytes(newPassword));
      return iv;
    }
    /// <summary>
    /// Create encryption key
    /// </summary>
    /// <param name="password">Password is used for random key generation</param>
    /// <returns></returns>
    private static CryptographicKey CreateKey(string password)
    {
       var provider = SymmetricKeyAlgorithmProvider.OpenAlgorithm("AES_CBC_PKCS7");
       var newPassword = password;
       // make sure we satify minimum length requirements
      while (newPassword.Length < provider.SupportedKeyLengths.Min)
      {
        newPassword = newPassword + password;
      }
      var buffer = CryptographicBuffer.ConvertStringToBinary(
        newPassword, BinaryStringEncoding.Utf8);
      buffer.Length = provider.SupportedKeyLengths.Min;
      var key = provider.CreateSymmetricKey(buffer);
      return key;
    }
  }
}

Using this class is very easy. Here is the example from my sample WinRT application.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Windows.Foundation;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Data;
namespace EncryptDecrypt
{
partial class MainPage
  {
    public MainPage()
    {
      InitializeComponent();
    }
    bool encrypt = true;
    private void Button_Click(object sender, RoutedEventArgs e)
    {
       var password = this.password.Text;
       if (encrypt)
       {
          this.input.Text = this.input.Text;
          this.ouput.Text = EncryptDecrypt.EncryptionProvider.Encrypt(this.input.Text, password);
          this.button.Content = "Decrypt";
          encrypt = false;
      }
      else
      {
          this.input.Text = this.ouput.Text;
          this.ouput.Text = EncryptDecrypt.EncryptionProvider.Decrypt(this.ouput.Text, password);
          this.button.Content = "Encrypt";
         encrypt = true;
      }
    }
  }
}

As you can see, my sample test app just calls two main methods and shows the results of encryption and decryption. Password and input string are required and cannot be left empty.

I am hoping to expand this in the future, once documentation is more complete, but this suffices for now. Please feel free to comment on this, especially if you have more information on the subject.

Thanks.

13 Comments

  1. You can see more if you do SymmetricKeyAlgorithmProvider.EnumerateAlgorithms(). Unfortunately in my case I get a not enough quota is available to perform this command exception when calling the exception. An error which doesn’t point me to any good direction.

  2. Hi,

    I get the following when trying to compile this class.

    Error 1 ‘Windows.Security.Cryptography.Core.SymmetricKeyAlgorithmProvider’ does not contain a definition for ‘SupportedKeyLengths’ and no extension method ‘SupportedKeyLengths’ accepting a first argument of type ‘Windows.Security.Cryptography.Core.SymmetricKeyAlgorithmProvider’ could be found (are you missing a using directive or an assembly reference?)

    Any suggestions?

  3. Here is the updated class for RTM

    using System;
    using System.Text;
    using Windows.Security.Cryptography;
    using Windows.Security.Cryptography.Core;
    using Windows.Storage.Streams;

    namespace App4
    {
    public static class EncryptionProvider
    {
    ///

    /// Encrypt a string
    ///

    /// String to encrypt
    /// Password to use for encryption
    /// Encrypted string
    public static string Encrypt(string input, string password)
    {
    //make sure we have data to work with
    if (string.IsNullOrEmpty(input))
    throw new ArgumentException(“input cannot be empty”);
    if (string.IsNullOrEmpty(password))
    throw new ArgumentException(“password cannot be empty”);
    // get IV, key and encrypt
    var iv = CreateInitializationVector(password);
    var key = CreateKey(password);
    var encryptedBuffer = CryptographicEngine.Encrypt(
    key, CryptographicBuffer.ConvertStringToBinary(input, BinaryStringEncoding.Utf8), iv);
    return CryptographicBuffer.EncodeToBase64String(encryptedBuffer);
    }
    ///

    /// Decrypt a string previously encrypted with Encrypt method and the same password
    ///

    /// String to decrypt
    /// Password to use for decryption
    /// Decrypted string
    public static string Decrypt(string input, string password)
    {
    //make sure we have data to work with
    if (string.IsNullOrEmpty(input))
    throw new ArgumentException(“input cannot be empty”);
    if (string.IsNullOrEmpty(password))
    throw new ArgumentException(“password cannot be empty”);
    // get IV, key and decrypt
    var iv = CreateInitializationVector(password);
    var key = CreateKey(password);
    var decryptedBuffer = CryptographicEngine.Decrypt(
    key, CryptographicBuffer.DecodeFromBase64String(input), iv);
    return CryptographicBuffer.ConvertBinaryToString(
    BinaryStringEncoding.Utf8, decryptedBuffer);
    }
    ///

    /// Create initialization vector IV
    ///

    /// Password is used for random vector generation
    /// Vector
    private static IBuffer CreateInitializationVector(string password)
    {
    var provider = SymmetricKeyAlgorithmProvider.OpenAlgorithm(“AES_CBC_PKCS7”);
    //create vector
    var iv = CryptographicBuffer.CreateFromByteArray(
    Encoding.UTF8.GetBytes(password));
    return iv;
    }
    ///

    /// Create encryption key
    ///

    /// Password is used for random key generation
    ///
    private static CryptographicKey CreateKey(string password)
    {
    var provider = SymmetricKeyAlgorithmProvider.OpenAlgorithm(“AES_CBC_PKCS7”);
    var buffer = CryptographicBuffer.ConvertStringToBinary(
    password, BinaryStringEncoding.Utf8);
    var key = provider.CreateSymmetricKey(buffer);
    return key;
    }
    }
    }

  4. Hi there,

    When I use the code last posted I get the error, below, at:

    “var key = provider.CreateSymmetricKey(buffer);”

    Any help would be greatly appreciated?

    System.ArgumentException was unhandled by user code
    HResult=-2147024809
    Message=Value does not fall within the expected range.
    Source=Windows.Security

  5. I am not sure that PKCS7 is suited for regular encryptions. It certainly has the advantage to do the necessary padding automatically for us. But PKCS7 is designed for signing messages in a public key infrastructure (http://en.wikipedia.org/wiki/PKCS). I assume that AES-CBC without PKCS7 is better suited for data encryption. Sadly we must do the padding ourselfs. I don’t know how to achieve that using the CryptographicBuffer class. But we could use the “old” way:

    // Append the padding (before encrypting)
    long remainder = data.Length % symmetricKeyAlgorithmProvider.BlockLength;
    long newSize = data.Length + symmetricKeyAlgorithmProvider.BlockLength – remainder;
    Array.Resize(ref data, (int)newSize); // Array.Resize adds elements storing 0

    // Remove the padding after encrypting
    int numberOfBytesToRemove = 0;
    for (int i = decryptedData.Length – 1; i >= 0; i–)
    {
    if (decryptedData[i] != 0)
    {
    break;
    }
    numberOfBytesToRemove++;
    }
    if (numberOfBytesToRemove > 0)
    {
    var decryptedDataWithRemovedPadding = new byte[decryptedData.Length – numberOfBytesToRemove];
    Array.Copy(decryptedData, decryptedDataWithRemovedPadding,
    decryptedData.Length – numberOfBytesToRemove);
    decryptedData = decryptedDataWithRemovedPadding;
    }

  6. @Neil Glastonbury: I figured it out. Each algorithm key needs a password length in order to work. The algorithm AES_CBC_PKCS7 needs a “password” of 32 in length.

    You can also use the create random key method:

    // Create a symmetric key.
    IBuffer keyMaterial = CryptographicBuffer.GenerateRandom(keyLength);
    key = objAlg.CreateSymmetricKey(keyMaterial);

Leave a Reply

Your email address will not be published. Required fields are marked *