D.R.
D.R.

Reputation: 21194

RSACryptoServiceProvider vs RSACng

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?

Upvotes: 1

Views: 3947

Answers (2)

bartonjs
bartonjs

Reputation: 33108

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));
        }
    }
}

Upvotes: 3

D.R.
D.R.

Reputation: 21194

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.

Upvotes: -1

Related Questions