Search code examples
c#.net-corecryptography.net-standardrsacryptoserviceprovider

X509Certificate2.PrivateKey as RSACryptoServiceProvider returns null in .Net Standards 2.1


I am writing a .Net Standard 2.1 class library that implements some cryptographic logic. I need to create a RSACryptoServiceProvider object from .pfx file to create a signature for my OpenIdConnect provider. I use the following code to do that.

X509Certificate2 certificate = new X509Certificate2(filePath, password, 
X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.Exportable);
var privateKey = certificate.PrivateKey as RSACryptoServiceProvider;

RSACryptoServiceProvider rsaProvider = new RSACryptoServiceProvider();
rsaProvider.FromXmlString(privateKey .ToXmlString(true));
var rsaFormatter = new RSAPKCS1SignatureFormatter(rsaProvider);
rsaFormatter.SetHashAlgorithm("SHA256");
var sha256 = new SHA256Managed();
var hashSignatureBytes 
=rsaFormatter.CreateSignature(sha256.ComputeHash(Encoding.UTF8.GetBytes(input)));
hashSignatureBase64 = Convert.ToBase64String(hashSignatureBytes);
return hashSignatureBase64;

This code is working in .Net 4.7 perfectly. But it does not work in .Net Standards 2.1 the reason is

var privateKey = certificate.PrivateKey as RSACryptoServiceProvider;

returns null

Does anyone know how to resolve this in .Net Standards? Thanks in advance


Solution

  • The cause of the problem is that the type of certificate.PrivateKey depends on the environment and is not necessarily RSACryptoServiceProvider, so the cast fails and as returns null.

    Example: certificate.PrivateKey is of type RSACryptoServiceProvider on .NET Framework 4.7.2 (.NET Standard 2.0), of type RSACng on .NET Core 3.0 or .NET 6.0 (.NET Standard 2.1) on Windows (10) and of type RSAOpenSsl on Linux/Ubuntu (for an overview of .NET Standard versions see here).

    To fix the issue, use GetRSAPrivateKey() instead, which is supported in .NET Standard 2.0 and 2.1. This returns an RSA implementation supported by the respective environment (with RSA as base class), which can be passed directly to the formatter class:

    var privateKey = certificate.GetRSAPrivateKey();
    var rsaFormatter = new RSAPKCS1SignatureFormatter(privateKey);
    

    Note that PrivateKey should be avoided because it is deprecated.


    Apart from that, signing is more convenient with RSA#SignData() (though this is a matter of opinion):

    byte[] hashSignatureBytes = privateKey.SignData(Encoding.UTF8.GetBytes(input), HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);