0

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.

videoguy
  • 1,732
  • 2
  • 24
  • 49
  • As long as you give the same name it should use the same key across restarts. Unless that name is the empty string, since that means “don’t persist” – bartonjs Jul 30 '22 at 00:30
  • I printed private key as hex string across restarts. It is different each time. I am pulling hair – videoguy Aug 01 '22 at 21:01

1 Answers1

1

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),
    },
};
bartonjs
  • 30,352
  • 2
  • 71
  • 111
  • Thanks a ton @bartonjs! That did the trick. But I am getting an exception on line "privateKey = rsaKey.Key.Export(CngKeyBlobFormat.GenericPrivateBlob);" saying the op is not allowed. This worked fine before. – videoguy Aug 02 '22 at 03:48
  • 1
    @videoguy Yeah, if you want the key to be (private key) exportable you need to specify that as an ExportPolicy in the creation parameters. It worked before because the ephemeral key created by RSACng is exportable. – bartonjs Aug 02 '22 at 04:19
  • I have different question like this, but for doing encryption/decryption across multiple OS instances. Wondering if you can chime in on https://stackoverflow.com/questions/73213622/how-to-encrypt-data-in-one-instance-of-windows-and-decrypt-in-different-os-insta – videoguy Aug 02 '22 at 20:50