Unable to import a key into KeyNumber.Signature with RSACryptoServiceProvider

Hi all,

The other day a customer of mine was having an issue when importing key pairs with .NET's RSACryptoServiceProvider. When setting KeyNumber parameter to KeyNumber.Exchange everything seems to be fine. But when setting the KeyNumber parameter to KeyNumber.Signature the key pair is imported to the KeyNumber.Exchange slot just as if you haven’t set the KeyNumber parameter.

 I could reproduce the issue with the following sample:

 private void button1_Click(object sender, EventArgs e)

{

    // WORKS

    string keypair1 = GetKey(KeyNumber.Exchange);

    StoreKeyInContainer(KeyNumber.Exchange, keypair1);

    CheckKey(KeyNumber.Exchange, keypair1);



    // FAILS BUT IT SHOULD WORK

    string keypair2 = GetKey(KeyNumber.Signature);

    StoreKeyInContainer(KeyNumber.Signature, keypair2);

    CheckKey(KeyNumber.Signature, keypair2);



    // WORKS BUT IT SHOULD FAIL

    string keypair3 = GetKey(KeyNumber.Signature);

    StoreKeyInContainer(KeyNumber.Signature, keypair3);

    CheckKey(KeyNumber.Exchange, keypair3);

}



public static string GetKey(KeyNumber theKeyNumber)

{

    CspParameters parms;

    RSACryptoServiceProvider rsa;



    parms = new CspParameters(1);

    parms.Flags = CspProviderFlags.UseMachineKeyStore;

    parms.KeyNumber = (Int32)theKeyNumber;

    parms.KeyContainerName = "Origin";

    rsa = new RSACryptoServiceProvider(parms);

    return rsa.ToXmlString(true);

}



public static void StoreKeyInContainer(KeyNumber theKeyNumber, String theXmlKeyPair)

{

    CspParameters  parms;

    RSACryptoServiceProvider rsa;



    parms = new CspParameters(1);

    parms.Flags = CspProviderFlags.UseMachineKeyStore;

    parms.KeyContainerName = "Test";

    parms.KeyNumber = (Int32) theKeyNumber;

    rsa = new RSACryptoServiceProvider(parms);

    rsa.FromXmlString(theXmlKeyPair);

}



public static void CheckKey(KeyNumber theKeyNumber, String theXmlKeyPair)

{

    CspParameters  parms;

    RSACryptoServiceProvider rsa;



    parms = new CspParameters(1);

    parms.Flags = CspProviderFlags.UseMachineKeyStore;

    parms.KeyContainerName = "Test";

    parms.KeyNumber = (Int32) theKeyNumber;

    rsa = new RSACryptoServiceProvider(parms);

    if (theXmlKeyPair.Equals(rsa.ToXmlString(true)))

    {

        MessageBox.Show("Success!");

    }

    else

    {

        MessageBox.Show("Failure!");

    }

}

We checked RSACryptoServiceProvicer's source code (e.g. with Reflector) and saw that RSACryptoServiceProvicer.ImportParameters always calls Utils._ImportKey with CALG_RSA_KEYX. We never use CALG_RSA_SIGN.

This issue happens (at least) on .NET Framework 2.0/3.5/4.0.

I talked to our Product Group about this, and they confirmed that this is issue is by design. They intentionally ignore KeyNumber param.

The good news is that there is a workaround. As we've seen, RSACryptoServiceProvider is hard-coded to use CALG_RSA_KEYX for importing RSA parameters.  What we can do, however, is import a raw CAPI key blob into either key number. Unfortunatelly, there is no managed API that builds that up for us, so we are going to have to do that ourselves.  The format is documented here: Private Key BLOBs. By setting the aiKeyAlgID field of the BLOBHEADER to either CALG_RSA_KEYX or CALG_RSA_SIGN you can pick which portion of the key container your blob will go into.

I did something similar in the past to import a key blob in .NET. I have attached a sample that uses CryptoAPI via P/Invoke to be able to verify signatures using a temporary keyset: How to verify signatures using a temporary keyset in .NET. Check method button22_Click for the verification code. There I create a key blob from an XML and import it via CryptoAPI.

I hope this helps.

Regards,

 

Alex (Alejandro Campos Magencio)