How to perform RSA data encryption with x509 certificate based key in Windows Store application   

 

 

Windows Store application (Windows Runtime) has provided rich support on cryptography and PKI programming. We can use the built-in classes to perform common crypto operations like symmetric & asymmetric encryption, digital signing, hashing, etc… (see reference below):

 

#Crypto and PKI application capabilities (Windows)

http://msdn.microsoft.com/en-us/library/windows/apps/hh464922.aspx

 

And the Windows SDK sample collection has also provided a detailed sample to demonstrate various crypto programming cases supported in Windows Store application.

 

#CryptoWinRT sample

http://code.msdn.microsoft.com/windowsapps/CryptoWinRT-54ff3d9f

 

However, the document and sample haven’t provided more information about how we can connect the crypto code to the X509 certificate which is usually used in PKI based crypto programming scenario. In a recent thread, someone has asked the question about how to load the public key from a x509 certificate (with RSA sha1 algorithm) and perform asymmetric encryption/signing with the key (see thread below):

 

http://social.msdn.microsoft.com/Forums/en-US/winappswithhtml5/thread/17e1467a-2de7-47d2-8d8c-130518eaac68

 

At first, I think the solution should be quite straightforward and might be resolved by a simple built-in function. But after some research, I found that there is no built-in class/method (in the current Windows Store/Windows Runtime library) which can directly extract the public key (or key pairs) from a given X509 certificate (.cer or .pfx file). Therefore, in this blog entry, I’ll try summarizing two approaches we can use for performing asymmetric encryption by using the public key from an x509 certificate in Windows Store application.

 

Pre-extract the public key in x509 certificate and save it in file (or code) for windows store app

In this case, we need to write some standard .NET code to extract the public key data from X509 certificate and save it (for later using in windows store app). Here is the sample code (in a standard .NET Winform app) for extracting and displaying the public key (in base64 format):

 

private void btnFindCert_Click(object sender, EventArgs e)

{

    var store = new X509Store(StoreName.My, StoreLocation.LocalMachine);

    store.Open(OpenFlags.ReadOnly);

 

    var certs = store.Certificates.Find(X509FindType.FindBySubjectName, txtSubjectName.Text, false);

 

    if (certs.Count <= 0)

    {

        MessageBox.Show("No Certificate found!");

        return;

    }

 

    var cert = certs[0];

    var sb = new StringBuilder();

 

    sb.AppendFormat("\r\nSubject Name:{0}\r\nThumbprint:{1}\r\nIssuer:{2}\r\n", cert.SubjectName.Name, cert.Thumbprint, cert.Issuer);

         

    var pubKeyStr = Convert.ToBase64String(cert.GetPublicKey());

    sb.AppendFormat("\r\nPublic Key (can be used in WinRT):{0}\r\n", pubKeyStr);

 

 

    txtOutput.AppendText(sb.ToString());

 

    store.Close();

}

 

 

After getting the public key data (bytes), we can directly load it in Windows Store app through AsymmetricAlgorithmProvider.importPublicKey method. Here is the complete code for loading the public key (bytes) and use it for data encryption in Windows Store app (javascript):

 

document.getElementById("btnEncryptByRawKey").addEventListener("click", function () {

 

                // Get from the WinFormCertTool

                var pubKeyStr = "MIGJAoGBAN34SRLiTkVvaUXXhLir3eGmEzV8M8x1Qf+WX6U67ML6tyzWN3oaMya94C1/G5VaG4fut9LpR/047rjuCJFZ3fItgOZXJJqzP6cX4lqVLj954IBQAJMrzzZrGhmBLuPjx9DcISYG+v0DlAj9gstxPPGFwjG2yJK9ADG/hIsi84OZAgMBAAE=";

                var pubKeyBytes = cryptoNS.CryptographicBuffer.decodeFromBase64String(pubKeyStr);

 

                var alg = cryptoNS.Core.AsymmetricKeyAlgorithmProvider.openAlgorithm(cryptoNS.Core.AsymmetricAlgorithmNames.rsaPkcs1);

                var pubKey = alg.importPublicKey(pubKeyBytes, cryptoNS.Core.CryptographicPublicKeyBlobType.pkcs1RsaPublicKey);

 

                var dataToEncrypt = document.getElementById("txtDataToSecure").value;

                var dataBytes = cryptoNS.CryptographicBuffer.convertStringToBinary(dataToEncrypt, cryptoNS.BinaryStringEncoding.utf8);

                var encryptedBytes = cryptoNS.Core.CryptographicEngine.encrypt(pubKey, dataBytes, null);

                var encryptedData = cryptoNS.CryptographicBuffer.encodeToBase64String(encryptedBytes);

 

                txtOutput.value = "Encrypted data:" + encryptedData;

                console.log(txtOutput.value);

 

            });

 

Manually parse the X509 certificate content (.cer file) and extract the public key in Windows Store app

If we cannot pre-extract the public key information from certificate and have to load the key info in Windows Store app directly, then we have to manually parse the X509 certificate data (.cer file). Thanks for Carlos who has provided a helper class which can be used in Windows Store app (in a Windows Runtime library) for extracting public key from x509 certificate binary content:

 

http://social.msdn.microsoft.com/Forums/en-US/winappswithhtml5/thread/17e1467a-2de7-47d2-8d8c-130518eaac68

 

Here is the complete code of the class/functions:

 

using System;

using System.Collections.Generic;

using System.Linq;

using System.Runtime.InteropServices.WindowsRuntime;

using System.Text;

using System.Threading.Tasks;

using Windows.Security.Cryptography;

using Windows.Security.Cryptography.Certificates;

using Windows.Security.Cryptography.Core;

 

 

namespace RSACertLib

{

    public sealed class RSAUtils

    {

       

        public static CryptographicKey GetCryptographicPublicKeyFromCert(string strCert)

        {

            int length;

            CryptographicKey CryptKey = null;

 

            byte[] bCert = Convert.FromBase64String(strCert);

 

            // Assume Cert contains RSA public key

            // Find matching OID in the certificate and return public key

            byte[] rsaOID = EncodeOID("1.2.840.113549.1.1.1");

            int index = FindX509PubKeyIndex(bCert, rsaOID, out length);

 

            // Found X509PublicKey in certificate so copy it.

            if (index > -1)

            {

                byte[] X509PublicKey = new byte[length];

                Array.Copy(bCert, index, X509PublicKey, 0, length);

 

                AsymmetricKeyAlgorithmProvider AlgProvider = AsymmetricKeyAlgorithmProvider.OpenAlgorithm(AsymmetricAlgorithmNames.RsaPkcs1);

                CryptKey = AlgProvider.ImportPublicKey(CryptographicBuffer.CreateFromByteArray(X509PublicKey));

            }

 

            return CryptKey;

        }

 

        static int FindX509PubKeyIndex(byte[] Reference, byte[] value, out int length)

        {

            int index = -1;

            bool found;

            length = 0;

 

            for (int n = 0; n < Reference.Length; n++)

            {

                if ((Reference[n] == value[0]) && (n + value.Length < Reference.Length))

                {

                    index = n;

                    found = true;

 

                    for (int m = 1; m < value.Length; m++)

                    {

                        if (Reference[n + m] != value[m])

                        {

                            found = false;

                            break;

                        }

                    }

 

                    if (found) break;

                    else index = -1;

                }

            }

 

            if (index > -1)

            {

                // Find outer Sequence

                while (index > 0 && Reference[index] != 0x30) index--;

                index--;

                while (index > 0 && Reference[index] != 0x30) index--;

            }

 

            if (index > -1)

            {

                // Find the length of encoded Public Key

                if ((Reference[index + 1] & 0x80) == 0x80)

                {

                    int numBytes = Reference[index + 1] & 0x7F;

                    for (int m = 0; m < numBytes; m++)

                    {

                        length += (Reference[index + 2 + m] << ((numBytes - 1 - m) * 8));

                    }

 

                    length += 4;

                }

                else

                {

                    length = Reference[index + 1] + 2;

                }

            }

 

            return index;

        }

 

        static public byte[] EncodeOID(string szOID)

        {

            int[] OIDNums;

            byte[] pbEncodedTemp = new byte[64];

            byte[] pbEncoded = null;

            int n, index, num, count;

 

            OIDNums = ParseOID(szOID);

 

            pbEncodedTemp[0] = 6;

 

            pbEncodedTemp[2] = Convert.ToByte(OIDNums[0] * 40 + OIDNums[1]);

            count = 1;

 

            for (n = 2, index = 3; n < OIDNums.Length; n++)

            {

                num = OIDNums[n];

 

                if (num >= 16384)

                {

                    pbEncodedTemp[index++] = Convert.ToByte(num / 16384 | 0x80);

                    num = num % 16384;

 

                    count++;

                }

 

                if (num >= 128)

                {

                    pbEncodedTemp[index++] = Convert.ToByte(num / 128 | 0x80);

                    num = num % 128;

 

                    count++;

                }

 

 

                pbEncodedTemp[index++] = Convert.ToByte(num);

                count++;

            }

 

            pbEncodedTemp[1] = Convert.ToByte(count);

 

            pbEncoded = new byte[count + 2];

            Array.Copy(pbEncodedTemp, 0, pbEncoded, 0, count + 2);

 

            return pbEncoded;

        }

 

        static public int[] ParseOID(string szOID)

        {

            int nlast, n = 0;

            bool fFinished = false;

            int count = 0;

            int[] dwNums = null;

 

            do

            {

                nlast = n;

                n = szOID.IndexOf(".", nlast);

                if (n == -1) fFinished = true;

                count++;

                n++;

            } while (fFinished == false);

 

            dwNums = new int[count];

 

            count = 0;

            fFinished = false;

 

            do

            {

                nlast = n;

                n = szOID.IndexOf(".", nlast);

                if (n != -1)

                {

                    dwNums[count] = Convert.ToInt32(szOID.Substring(nlast, n - nlast), 10);

                }

                else

                {

                    fFinished = true;

                    dwNums[count] = Convert.ToInt32(szOID.Substring(nlast, szOID.Length - nlast), 10);

                }

 

                n++;

                count++;

            } while (fFinished == false);

 

            return dwNums;

        }

 

 

    }

}

 

 

Then, we can use this class in our Windows Store app to extract public key (from a given x509 certificate file) and perform RSA encryption (see javascript code below):

 

document.getElementById("btnEncryptByCertFile").addEventListener("click", function () {

 

    // Use file picker to select a x509 certificate file (.cer file)

    var openPicker = new Windows.Storage.Pickers.FileOpenPicker();

    openPicker.viewMode = Windows.Storage.Pickers.PickerViewMode.list;

    openPicker.fileTypeFilter.replaceAll([".cer"]);

 

    openPicker.pickSingleFileAsync().then(function (file) {

        return Windows.Storage.FileIO.readBufferAsync(file);

    }).done(

        function (buffer) {

                            

            var isBase64Cert = document.getElementById("rdCertBASE64").checked;

 

            var certStr = "";

            if (isBase64Cert) {

 

                // Base 64 format cer, need to extract the binary part

                certStr = cryptoNS.CryptographicBuffer.convertBinaryToString(cryptoNS.BinaryStringEncoding.utf8, buffer);

                certStr = certStr.replace("-----BEGIN CERTIFICATE-----", "");

                certStr = certStr.replace("-----END CERTIFICATE-----", "");

                certStr = certStr.trim();

 

 

            } else {

                // For DER binary format .cer file

                certStr = cryptoNS.CryptographicBuffer.encodeToBase64String(buffer);

            }

 

            // Extract the public key from cert binary

            var pubKey = RSACertLib.RSAUtils.getCryptographicPublicKeyFromCert(certStr);

 

            

            var dataToEncrypt = "This is an apple!";

            var bytesToEncrypt = cryptoNS.CryptographicBuffer.convertStringToBinary(dataToEncrypt, cryptoNS.BinaryStringEncoding.utf8);

 

            var encryptedBytes = cryptoNS.Core.CryptographicEngine.encrypt(pubKey, bytesToEncrypt, null);

            var encryptedData = cryptoNS.CryptographicBuffer.encodeToBase64String(encryptedBytes);

 

            txtOutput.value = "encryptedData:" + encryptedData;

            console.log("encryptedData:" + encryptedData);

        },

        function (err) {

            txtOutput.value = err;

            console.log(err);

        }

    );

 

});

 

 

For how to create Windows Runtime library and use it in Windows Store javascript app, you can refer to the following reference:

 

#Walkthrough: Creating a simple component in C# or Visual Basic and calling it from JavaScript

http://msdn.microsoft.com/en-us/library/windows/apps/hh779077.aspx      

Test certificates and code used here

The certificate used in the sample code above can be generated via the following command (using makecert.exe too):

 

#Creating a Root Certificate Authority

 

makecert.exe -n "CN=RSA TEST CA,O=Organization,OU=Org Unit,L=Test Center,S=CA,C=US" -pe -ss my -sr LocalMachine -sky exchange -m 96 -a sha1 -len 2048 -r C:\workspace\demo\WinStoreRSACertificateDemo\RSA_TEST_CA.cer

 

 

#Create a Server Certificate issued from the previously created Certificate Authority

 

makecert.exe -n "CN=RSA TEST SERVER" -pe -ss my -sr LocalMachine -sky exchange -m 96 -in "RSA TEST CA" -is my -ir LocalMachine -a sha1 -eku 1.3.6.1.5.5.7.3.1,1.3.6.1.5.5.7.3.2 C:\workspace\demo\WinStoreRSACertificateDemo\RSA_TEST_SERVER.cer

 

 

All the .NET and Windows Store sample code mentioned above can be got in the attached package in this blog entry.