Reputation: 21194
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
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
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