3

I have an exported RSAParameters private key that I'd like to import into another machine. I can save new keys into the local machine or user containers, but I'm stuck trying to import an existing key.

The code below will generate a new key pair, and I know I could just generate a new key directly into the container - but I want to be able to generate a single key and import that same key into a handful of different computers.

How can I take either an RSAParameters or a string of XML (either one) and import that into a local user (or machine) container?

    public async Task<KeyGenerationResult> GenerateNewKeyAsync(int keySize)
    {
        var csp = new RSACng(keySize);

        var privKey = await Task.Run(() => csp.ExportParameters(includePrivateParameters: true));
        var pubKey = csp.ExportParameters(includePrivateParameters: false);

        var pubKeyString = exportKeyToString(pubKey);
        var privKeyString = exportKeyToString(privKey);

        return new KeyGenerationResult
        {
            PrivateKey = privKey,
            PublicKey = pubKey,
            PrivateKeyCleartext = privKeyString,
            PublicKeyCleartext = pubKeyString
        };
    }

    private static string exportKeyToString(RSAParameters key)
    {
        string keyString;
        var sw = new StringWriter();
        var xs = new XmlSerializer(typeof(RSAParameters));
        xs.Serialize(sw, key);
        keyString = sw.ToString();
        return keyString;
    }

    public void SavePrivateKeyToLocalMachine(RSAParameters privateKey, string keyName)
    {
       //Stuck here. :(
    }

CngKey.Import() takes a byte[] and that looks promising, but I haven't been able to find any method to create the byte[] that CngKey.Import() requires.

        var d = new RSACryptoServiceProvider();
        d.ImportParameters(privateKey);
        var keyBlob = d.ExportCspBlob(true);
        var key = CngKey.Import(keyBlob, CngKeyBlobFormat.Pkcs8PrivateBlob);

That gets me a byte[] but regardless of what CngKeyBlobFormat I use, I get an exception. I'm stuck.

UPDATE

I found a way to get a byte[] using

 var cp = new CngKeyCreationParameters();
 cp.KeyUsage = CngKeyUsages.AllUsages;
 cp.ExportPolicy = CngExportPolicies.AllowPlaintextExport | CngExportPolicies.AllowExport | CngExportPolicies.AllowArchiving | CngExportPolicies.AllowPlaintextArchiving;
 cp.Parameters.Add(new CngProperty("Length", BitConverter.GetBytes(keySize), CngPropertyOptions.None));
 var key = CngKey.Create(CngAlgorithm.Rsa, null, cp);
 var bytes = key.Export(CngKeyBlobFormat.{I have tried them all});

And this code looks like it should let me import the byte[]

 /* try to save this key to the local user container */
 var keyParameters = new CngKeyCreationParameters()
 {
     KeyCreationOptions = CngKeyCreationOptions.OverwriteExistingKey,
     Provider = CngProvider.MicrosoftSoftwareKeyStorageProvider,
     KeyUsage = CngKeyUsages.AllUsages,
     ExportPolicy = CngExportPolicies.AllowPlaintextExport
 };
 keyParameters.KeyCreationOptions = CngKeyCreationOptions.None;
 keyParameters.Parameters.Add(new CngProperty("Length", BitConverter.GetBytes(keySize), CngPropertyOptions.None));
 keyParameters.Parameters.Add(new CngProperty(blobType.Format, bytes, CngPropertyOptions.None));

 var newKey = CngKey.Create(CngAlgorithm.Rsa, "MyTestName", keyParameters);

...but once again, no dice. It doesn't matter what CngKeyBlobFormat I try, they all give me exceptions and I'm unable to import the key into a local key storage provider.

What magic mix of settings and parameters do I need to make this work?

J. Allen
  • 602
  • 1
  • 7
  • 24
  • What exceptions were you getting exactly? Not only would that help people help you, it would make your question (and more importantly your solution!) more discoverable by people facing the same issue. – Ohad Schneider Sep 07 '17 at 15:28

1 Answers1

4

Well, I finally got it working. Here's the code as it finally settled down.

public class KeyGenerationResult
{
    public RSAParameters PublicKey { get; set; }
    public string PublicKeyCleartext { get; set; }
    public string PrivateKeyCleartext { get; set; }
    public byte[] PrivateBytes { get; set; }
    public int KeySize { get; set; }
    public CngKeyBlobFormat BlobFormat { get; set; }
}

    public async Task<KeyGenerationResult> GenerateNewKeyAsync(int keySize)
    {
        var cp = new CngKeyCreationParameters();
        cp.KeyUsage = CngKeyUsages.AllUsages;
        cp.ExportPolicy = CngExportPolicies.AllowPlaintextExport | CngExportPolicies.AllowExport | CngExportPolicies.AllowArchiving | CngExportPolicies.AllowPlaintextArchiving;
        cp.Parameters.Add(new CngProperty("Length", BitConverter.GetBytes(keySize), CngPropertyOptions.None));
        var key = await Task.Run(() => CngKey.Create(CngAlgorithm.Rsa, null, cp)).ConfigureAwait(false);
        var blobType = CngKeyBlobFormat.GenericPrivateBlob;
        var bytes = await Task.Run(() => key.Export(blobType)).ConfigureAwait(false);

        var rsa = new RSACng(key);
        var pubKey = rsa.ExportParameters(includePrivateParameters: false);
        var pubKeyString = exportKeyToString(pubKey);

        return new KeyGenerationResult
        {
            PublicKey = pubKey,
            PrivateKeyCleartext = Convert.ToBase64String(bytes),
            PublicKeyCleartext = pubKeyString,
            PrivateBytes = bytes,
            BlobFormat = blobType,
            KeySize = keySize
        };
    }

    private static string exportKeyToString(RSAParameters key)
    {
        string keyString;
        var sw = new StringWriter();
        var xs = new XmlSerializer(typeof(RSAParameters));
        xs.Serialize(sw, key);
        keyString = sw.ToString();
        return keyString;
    }

    public void SavePrivateKeyToLocalMachine(KeyGenerationResult keyData, string keyName)
    {
        var myKSP = CngProvider.MicrosoftSoftwareKeyStorageProvider;
        const bool MachineKey = false;

        if (!CngKey.Exists(keyName, myKSP))
        {
            var keyParams = new CngKeyCreationParameters
            {
                ExportPolicy = CngExportPolicies.AllowPlaintextExport,
                KeyCreationOptions = (MachineKey) ? CngKeyCreationOptions.MachineKey : CngKeyCreationOptions.None,
                Provider = myKSP
            };
            keyParams.Parameters.Add(new CngProperty("Length", BitConverter.GetBytes(keyData.KeySize), CngPropertyOptions.None));
            keyParams.Parameters.Add(new CngProperty(keyData.BlobFormat.Format, keyData.PrivateBytes, CngPropertyOptions.None));

            CngKey.Create(CngAlgorithm.Rsa, keyName, keyParams);
        }
        else
        {
            throw new CryptographicException($"The key with the name '{keyName}' already exists!");
        }
    }
J. Allen
  • 602
  • 1
  • 7
  • 24
  • For reference, I believe this was the inspiration for this answer: https://social.msdn.microsoft.com/Forums/vstudio/en-US/467485eb-1249-406e-86c8-289eb42bd51b/net-cng-full-backup-recovery-of-rsa-keypair-cngkeyimport-cngkeyblobformat-?forum=netfxbcl – Ohad Schneider Sep 06 '17 at 23:43