rsatpmcngwincrypt

How to encrypt data in one app and decrypt it in different Windows app with RSA keys tied to local system?


I have a setup where I need to encrypt blob of data in one app and decrypt it in different app.

I built a sample app that creates a named CngKey object. Then create a RSACng using CngKey object. Then use RSACng object to do encryption/decryption. What I found is that the key changes across restarts of the application even though it is loaded using the name it was created with. I am lost trying to understand the relation between CngKey and RSACng objects.

Below is snippet of code that describes what I am trying to do:

using System;
using System.IO;
using System.Security.Cryptography;

namespace TPMCrypto
{
    class Program
    {
        static byte[] data = { 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20 };

        static byte[] privateKey;
        private static byte[] encrypted;
        private static byte[] decrypted;
        

        static void Main(string[] args)
        {
            const string MyKey = "MyRSAKey";
            CngKey cngKey = null;
            string cmd = args.Length > 0 ? args[0] : "";

            try
            {
                CngKeyCreationParameters cng = new CngKeyCreationParameters
                {
                    KeyUsage = CngKeyUsages.AllUsages,
                    KeyCreationOptions = CngKeyCreationOptions.MachineKey,
                    Provider = CngProvider.MicrosoftSoftwareKeyStorageProvider
                };

                if (!CngKey.Exists(MyKey, CngProvider.MicrosoftSoftwareKeyStorageProvider, CngKeyOpenOptions.MachineKey))
                {
                    Console.WriteLine("Creating rsaKey");
                    cngKey = CngKey.Create(CngAlgorithm.Rsa, MyKey, cng);
                }
                else
                {
                    Console.WriteLine("Opening rsaKey");
                    cngKey = CngKey.Open(MyKey, CngProvider.MicrosoftSoftwareKeyStorageProvider, CngKeyOpenOptions.MachineKey);
                }

                RSACng rsaKey = new RSACng(cngKey)
                {
                    KeySize = 2048
                };

                privateKey = rsaKey.Key.Export(CngKeyBlobFormat.GenericPrivateBlob);
                string prvResult = ByteArrayToHexString(privateKey, 0, privateKey.Length);

                Console.WriteLine("\nPrivate key - length = " + privateKey.Length + "\n" + prvResult + "\n");

                const string FILE_PATH = @"\temp\tpmtests\encryptedblob.dat";

                // Encrypt / decrypt
                if (cmd == "readfromfile")
                {
                    
                    Directory.CreateDirectory(Path.GetDirectoryName(FILE_PATH));
                    encrypted = File.ReadAllBytes(FILE_PATH);
                }
                else if (cmd == "deletekey")
                {
                    cngKey.Delete();
                    return;
                }
                else
                {
                    encrypted = Encrypt(rsaKey, data);
                    Console.WriteLine("The encrypted blob: ");
                    Console.WriteLine(ByteArrayToHexString(encrypted, 0, encrypted.Length));
                    File.WriteAllBytes(FILE_PATH, encrypted);
                }

                
                decrypted = Decrypt(rsaKey, encrypted);

                bool result = ByteArrayCompare(data, decrypted);

                if (result)
                    Console.WriteLine("Encrypt / decrypt works");
                else
                    Console.WriteLine("Encrypt / decrypt fails");
            }

            catch (Exception e)
            {
                Console.WriteLine("Exception " + e.Message);
            }
            finally
            {
                if (cngKey != null)
                    cngKey.Dispose();
            }

            Console.ReadLine();
        }

        static bool ByteArrayCompare(byte[] a1, byte[] a2)
        {
            if (a1.Length != a2.Length)
                return false;

            for (int i = 0; i < a1.Length; i++)
                if (a1[i] != a2[i])
                    return false;

            return true;
        }

        public static string ByteArrayToHexString(byte[] bytes, int start, int length)
        {
            string delimitedStringValue = BitConverter.ToString(bytes, start, length);
            return delimitedStringValue.Replace("-", "");
        }

        public static byte[] Sign512(byte[] data, byte[] privateKey)
        {
            CngKey key = CngKey.Import(privateKey, CngKeyBlobFormat.GenericPrivateBlob);
            RSACng crypto = new RSACng(key);
            return crypto.SignData(data, HashAlgorithmName.SHA512, RSASignaturePadding.Pkcs1);
        }

        public static bool VerifySignature512(byte[] data, byte[] signature, byte[] publicKey)
        {
            CngKey key = CngKey.Import(publicKey, CngKeyBlobFormat.GenericPublicBlob);
            RSACng crypto = new RSACng(key);
            return crypto.VerifyData(data, signature, HashAlgorithmName.SHA512, RSASignaturePadding.Pkcs1);
        }

        public static byte[] Encrypt(byte[] publicKey, byte[] data)
        {
            CngKey key = CngKey.Import(publicKey, CngKeyBlobFormat.GenericPublicBlob);
            RSACng crypto = new RSACng(key);
            var result = Encrypt(crypto, data);
            return result;
        }

        public static byte[] Encrypt(RSACng crypto, byte[] data)
        {
            if (null == crypto)
                return null;
            var result = crypto.Encrypt(data, RSAEncryptionPadding.OaepSHA512);
            return result;
        }

        public static byte[] Decrypt(byte[] privateKey, byte[] data)
        {
            CngKey key = CngKey.Import(privateKey, CngKeyBlobFormat.GenericPrivateBlob);
            RSACng crypto = new RSACng(key);
            var result = Decrypt(crypto, data);
            return result;
        }

        public static byte[] Decrypt(RSACng aKey, byte[] data)
        {
            if (null == aKey)
                return null;
            var result = aKey.Decrypt(data, RSAEncryptionPadding.OaepSHA512);
            return result;
        }

    }
}

I am aware of dpapi and how to do this using it. I don't want to use it for this, please don't point me in that direction. I am using CNG flavor of crypto to force C# use NCryptXYZ crypto calls and the desire is to secure the keys in TPM.


Solution

  • Ah, looking at your code again, you've made a goof.

    RSACng rsaKey = new RSACng(cngKey)
    {
        KeySize = 2048
    };
    

    Setting the KeySize property on an RSACng does one of two things:

    1. If get_KeySize == value, ignore the input, do nothing.
    2. Else, detach from the current key and the next time the key is used, generate a new key of get_KeySize at the time.

    So you're opening an existing key, then discarding it, and generating a new ephemeral key. (Which you could see by checking rsaKey.Key.Name, it won't match your input).

    Presumably you did this as a way to create the key with the right size in the first place, but you're too late. The correct way is

    CngKeyCreationParameters cng = new CngKeyCreationParameters
    {
        KeyUsage = CngKeyUsages.AllUsages,
        KeyCreationOptions = CngKeyCreationOptions.MachineKey,
        Provider = CngProvider.MicrosoftSoftwareKeyStorageProvider,
        Parameters =
        {
            new CngProperty("Length", BitConverter.GetBytes(2048), CngPropertyOptions.Persist),
        },
    };