[Windows Store App]How to perform RSA data encryption with x509 certificate based key in Windows Store application
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)
https://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
https://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):
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:
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
https://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.