Search code examples
c#rsadigital-signatureprivate-keycng

Get X509Certificate2 Private Key without Export as CngKey


I'm using the ncrypt.dll in order to create a hash using both MD5 + SHA1 for DTLS like this:

private static class NCryptInterop
{
    private struct BCRYPT_PKCS1_PADDING_INFO
    {
        internal IntPtr pszAlgId;
    }

    [Flags]
    private enum NCryptSignFlags
    {
        BCRYPT_PAD_PKCS1 = 2,
    }

    [DllImport("ncrypt.dll")]
    private static extern int NCryptSignHash(
        SafeNCryptKeyHandle hKey,
        ref BCRYPT_PKCS1_PADDING_INFO padding,
        ref byte pbHashValue,
        int cbHashValue,
        ref byte pbSignature,
        int cbSignature,
        out int cbResult,
        NCryptSignFlags dwFlags);

    internal static byte[] SignHashRaw(CngKey key, byte[] hash, int keySize)
    {
        int keySizeBytes = keySize / 8;
        byte[] signature = new byte[keySizeBytes];

        // The Handle property returns a new object each time.
        using (SafeNCryptKeyHandle keyHandle = key.Handle)
        {
            // Leave pszAlgId NULL to "raw sign"
            BCRYPT_PKCS1_PADDING_INFO paddingInfo = new BCRYPT_PKCS1_PADDING_INFO();

            int result = NCryptSignHash(
                keyHandle,
                ref paddingInfo,
                ref hash[0],
                hash.Length,
                ref signature[0],
                signature.Length,
                out int cbResult,
                NCryptSignFlags.BCRYPT_PAD_PKCS1);

            if (result != 0)
            {
                throw new CryptographicException(result);
            }

            if (cbResult != signature.Length)
            {
                throw new InvalidOperationException();
            }

            return signature;
        }
    }
}

which I got from the answer of my previous question years ago (Thank you bartonjs!). In order to do that, I need a CngKey. I am currently getting the private key like this:

((RSACng)certificate.PrivateKey).Key;

which I know is now obsolete. I believe the new way is:

certificate.GetRSAPrivateKey();

but I have yet to find a way to convert from an RSA to a CngKey without exporting the certificate. Unfortunately, the certificate is not mine and I have no control over it, so I cannot just make it exportable and I do not have the password.

In .NET6 and 7 obtaining the private key the obsolete way works perfectly. However, in .NET8 something has changed and I get the exception Unable to cast object of type 'System.Security.Cryptography.RSABCrypt' to type 'System.Security.Cryptography.RSACng'.

Is there any way possible in .NET8 to get the private key as a CngKey without having to export? Or maybe ncrypt.dll can accept something other than a CngKey that I can get without having to export the private key?


Solution

  • I was able to finally figure this out. The exception I was getting was actually on getting the public key, not the private key so that simplified things a bit. This is how I was able to do it and it works for legacy .NET Standard 2.0 as well as .NET 6, 7, and 8

    var privateRsa = cert.GetRSAPrivateKey() ?? throw new Exception("Unable to get RSA Private Key");
    var privateRsaCng = privateRsa as RSACng ?? throw new Exception("Private Key is not an RSACng");
    _PrivateKeyRsa = privateRsaCng.Key;
    
    var publicRsa = cert.GetRSAPublicKey() ?? throw new Exception("Unable to get RSA Public Key");
    var publicRsaCng = new RSACng();
    publicRsaCng.ImportParameters(publicRsa.ExportParameters(false));
    PublicKey = publicRsaCng.Key;