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