1

I have (non-exportable) keys which have been created using RSACryptoServiceProvider. I want to sign data using RSA-PSS (which is not RSACryptoServiceProvider). Therefore I want to obtain the same private key as an RSACng instance.

I tried the following:

// Create key with RSACryptoServiceProvider
var keyId = Guid.NewGuid().ToString();
var providerName = "Microsoft Enhanced RSA and AES Cryptographic Provider";
var key = new RSACryptoServiceProvider(2048, new CspParameters(24) {
  ProviderName = providerName,
  KeyContainerName = keyId,
  KeyNumber = (int) KeyNumber.Signature,
  Flags = CspProviderFlags.UseNonExportableKey
});

// Obtain an RSACng reference:
var cngKey = CngKey.Open(keyId, new CngProvider(providerName));
var cngRsaKey = new RSACng(cngKey);

// Sign something using cngRsaKey
[...]

Unfortunately, it always fails when performing CngKey.Open with WindowsCryptographicException: Keyset does not exist.

How to open the previously created key with RSACng?

Note, that I cannot use the answer provided by https://stackoverflow.com/a/50703729/1400869 because I cannot use exportable private keys. In the end the keys should reside on an HSM (Hardware Security Module).

Any ideas?

D.R.
  • 20,268
  • 21
  • 102
  • 205
  • See if this helps : http://www.infinitec.de/post/2010/11/22/Setting-the-PIN-of-a-smartcard-programmatically.aspx – jdweng Apr 17 '20 at 16:11
  • I'm not sure how specifying a PIN programatically has anything to do with my question. Can you elaborate a bit more? – D.R. Apr 17 '20 at 16:13
  • Sorry. Working a smart card issue using RSA and got two post mixed up. Still you are setting the provider in a certificate. The PIN of a smartcard is the key in the certificate. A smart card uses a certificate. – jdweng Apr 17 '20 at 16:29
  • I'm only dealing with keys here, I don't see a certificate. – D.R. Apr 17 '20 at 16:30
  • Then how does a key also have a provider name? Don't you need a certificate loaded? – jdweng Apr 17 '20 at 16:41
  • That /should/ work... does it work better if you use an Exchange key? (Since you’re making new keys with this snippet...) If not, what version of Windows are you on? – bartonjs Apr 18 '20 at 22:46
  • @bartonjs: Unfortunately, no. I'm on Windows 10. – D.R. Apr 19 '20 at 12:15
  • After some more research, a colleague of mine pointed out that this is probably not possible at all using "normal" means. But! There is a procedure called "CngLightup" which obtains you a CngKey reference to a CSP (RSACryptoServiceProvider) reference. I will post it as an answer. – D.R. Apr 19 '20 at 12:17

2 Answers2

3

Apparently CngKey.Open doesn't work for CAPI RSA keys in the Signature slot (because it hard-codes the dwLegacyKeySpec value to 0). The easiest fix is to use the Exchange slot; but if you need to work with Signature-slot keys you can:

[DllImport("ncrypt.dll", CharSet = CharSet.Unicode)]
private static extern int NCryptOpenStorageProvider(
    out SafeNCryptProviderHandle phProvider,
    string pszProviderName,
    int dwFlags);

[DllImport("ncrypt.dll", CharSet = CharSet.Unicode)]
private static extern int NCryptOpenKey(
    SafeNCryptProviderHandle hProvider,
    out SafeNCryptKeyHandle phKey,
    string pszKeyName,
    int dwLegacyKeySpec,
    CngKeyOpenOptions dwFlags);

private static void Test61275795()
{
    const string KeyId = "test-982375";

    CspParameters cspParams = new CspParameters(24)
    {
        ProviderName = "Microsoft Enhanced RSA and AES Cryptographic Provider",
        KeyContainerName = KeyId,
        KeyNumber = (int)KeyNumber.Signature,
        Flags = CspProviderFlags.UseNonExportableKey,
    };

    using (RSACryptoServiceProvider rsaCsp = new RSACryptoServiceProvider(2048, cspParams))
    {
        // Because this is a test, delete the key on Dispose.
        rsaCsp.PersistKeyInCsp = false;

        SafeNCryptKeyHandle hKey;

        int dwError = NCryptOpenStorageProvider(out var hProv, cspParams.ProviderName, 0);

        using (hProv)
        {
            if (dwError != 0)
            {
                throw new CryptographicException(
                    $"{nameof(NCryptOpenStorageProvider)}: 0x{dwError:X8}");
            }

            dwError = NCryptOpenKey(hProv, out hKey, KeyId, cspParams.KeyNumber, 0);

            if (dwError != 0)
            {
                hKey.Dispose();
                throw new CryptographicException($"{nameof(NCryptOpenKey)}: 0x{dwError:X8}");
            }
        }

        using (hKey)
        using (CngKey cngKey = CngKey.Open(hKey, 0))
        using (RSACng rsaCng = new RSACng(cngKey))
        {
            byte[] sig = rsaCng.SignData(
                Array.Empty<byte>(),
                HashAlgorithmName.SHA256,
                RSASignaturePadding.Pss);

            Console.WriteLine(BitConverter.ToString(sig));
        }
    }
}
bartonjs
  • 30,352
  • 2
  • 71
  • 111
-1

There is a procedure called "CngLightup" which allows one to obtain a RSACng reference to a key created with RSACryptoServiceProvider. It is used by Microsoft, e.g., in their implementation of manifest signing: https://github.com/microsoft/referencesource/blob/master/inc/mansign2.cs#L1426

This requires you to have a certificate in your hands, not just a key reference. But it is easy to create a dummy self-signed certificate using the key at hand, then call CngLightup.GetRSAPrivateKey() and voila, if you check GetType(), you have an RSACng reference in your hands.

D.R.
  • 20,268
  • 21
  • 102
  • 205
  • 2
    CngLightup is just a helper utility for inside the framework to understand if it's "safe" to call (e.g.) `cert.GetRSAPrivateKey()` and do what's expected for the system; which is just due to how .NET 4.5.2 (no RSACng) and later versions (yes RSACng) handle sharing a GAC. The difference is in the plumbing behind `GetRSAPrivateKey()`, which I worked around in my answer. – bartonjs Apr 20 '20 at 16:41