Invalidcastexception: Unable to cast Object of type 'System.Security.Cryptography.Rsacng'

Asked

Viewed 968 times

3

I am trying to pass the PIN through the code, so that the user does not need to enter it always, it is returning me this error:

Invalidcastexception: Unable to cast Object of type 'System.Security.Cryptography.Rsacng'

This is the function I’m using:

public static RSACryptoServiceProvider LerDispositivo(RSACryptoServiceProvider key, string PIN)
{
    CspParameters csp = new CspParameters(key.CspKeyContainerInfo.ProviderType, key.CspKeyContainerInfo.ProviderName);
    SecureString ss = new SecureString();
    foreach (char a in PIN)
    {
        ss.AppendChar(a);
    }
    csp.ProviderName = key.CspKeyContainerInfo.ProviderName;
    csp.ProviderType = key.CspKeyContainerInfo.ProviderType;
    csp.KeyNumber = key.CspKeyContainerInfo.KeyNumber == KeyNumber.Exchange ? 1 : 2;
    csp.KeyContainerName = key.CspKeyContainerInfo.KeyContainerName;
    csp.KeyPassword = ss;
    csp.Flags = CspProviderFlags.NoPrompt | CspProviderFlags.UseDefaultKeyContainer;

    RSACryptoServiceProvider rsa = new RSACryptoServiceProvider(csp);
    return rsa;
}

And I added those three lines to my signing job:

RSACryptoServiceProvider Key = new RSACryptoServiceProvider();
                    Key = (System.Security.Cryptography.RSACryptoServiceProvider)x509Cert.PrivateKey;
                    signedXml.SigningKey = x509Cert.PrivateKey;
                    signedXml.SigningKey = LerDispositivo(Key, "senhaaqui");

Error occurs on this line:

 Key = (System.Security.Cryptography.RSACryptoServiceProvider)x509Cert.PrivateKey;

EDIT I have seen several examples on the Internet, but none of them solved my problem. I managed to find this link, that has an explanation, but I can not adapt to the code.

Following the LINK above, I tried to do this way:

RSACryptoServiceProvider publicKeyProvider = (System.Security.Cryptography.RSACryptoServiceProvider)x509Cert.GetRSAPrivateKey();
                        signedXml.KeyInfo = keyInfo;
                        signedXml.SigningKey = LerDispositivo(publicKeyProvider, "senhaaqui");

and it returns the same error:

Invalidcastexception: Unable to cast Object of type 'System.Security.Cryptography.Rsacng' to type 'System.Security.Cryptography.Rsacryptoserviceprovider'.

Is there any way to convert Rsacryptoserviceprovider to Rsacng ? Every way I try returns the same error.

EDIT

According to Pedro’s answer, I made the changes, but even so without success, it returns the following error:

error CS0433: Type "Cngpropertyoptions" exists in "System.Security.Cryptography.Algorithms, Version=4.3.1.0, Culture=neutral, Publickeytoken=b03f5f7f11d50a3a" and "System.Security.Cryptography.Cng, Version=4.3.1.0, Culture=neutral, Publickeytoken=b03f5f7f11d50a3a"

I tried several "solutions" that could solve the problem, but none solved the problem yet.

  • Apparently the property PrivateKey of the object x509Cert is not the type who is trying to do the Cast. Confirm in Debug if this is the guy.

  • @Joãomartins I saw some examples on the internet this way, there is some other way to pass the PIN via code ?

  • I’m not saying the code is wrong, but the Cast is for sure. You have to see what kind returns x509Cert.PrivateKey.

  • @Joãomartins I checked on Debug ParentWindowHandle = '((System.Security.Cryptography.RSACng)x509Cert.PrivateKey).Key.ParentWindowHandle' threw an exception of type 'Internal.Cryptography.CryptoThrowHelper.WindowsCryptographicException', realmente ele é RSACng, porém não sei como corrigir.

  • CryptographicException: The key value is not an RSA or DSA key or the key is unreadable. https://docs.microsoft.com/pt-br/dotnet/api/system.security.cryptography.x509certificates.x509certificate2.privatekey?view=netframework-4.7.2

  • @Joãomartins has some solution to help me ?? I could not solve it

  • @Leandroangelo took a look at the link, but it did not help me, I need to pass the PIN through the code, but every way I try, returns me this error, I do not know how to convert.

  • How about this: Key = (System.Security.Cryptography.RSACryptoServiceProvider).PrivateKey?

  • @Reginaldorigo the problem happens, because I need to pass the certificate PIN directly in the code, so that the user does not need to type every time to sign the file, but it seems that in older versions, this my code above works, and the most recent does not work, every way I try it returns me this same error.

  • Hmmmm. I have not tested and I have no way to test now. But I think that the way I passed you did not return the error Invalidcastexception. Or it will come back null or the key. Or any exception for trying to access the variable of an unspecified object.

  • @Reginaldorigo I’ve tried this way, I need to put the certificate before PrivateKey, the mistake he returned to me was: InvalidCastException: Unable to cast object of type 'System.Security.Cryptography.RSACng' to type 'System.Security.Cryptography.RSACryptoServiceProvider'.

  • Last try. Key = (System.Security.Cryptography.RSACryptoServiceProvider)(x509Cert.PrivateKey)

  • @Reginaldorigo same thing, I looked for examples of RSACng but unsuccessfully, I am failing to solve this problem.

Show 8 more comments

1 answer

6


On Windows there are two generations of encryption Apis:

  • The old generation (link), which includes Cryptographic Service Providers (CSP), which is the technology used by the class RSACryptoServiceProvider;
  • And the new generation (link), Cryptography API: Next Generation (CNG), which is the technology used by the class RSACng.

The estate X509Certificate2.PrivateKey returns an object of type AsymmetricAlgorithm, which may be of the RSA or DSA. From . NET Framework 4.6 it is possible to use the extension method X509Certificate2.GetRSAPrivateKey, that returns a direct object RSA.

A certificate key can implement the two cryptographic generations, or just one of them. In the case of your certificate, it seems that only the new cryptographic generation is implemented, so the property X509Certificate2.PrivateKey is returning an object of the type RSACng, which cannot be directly converted to the type RSACryptoServiceProvider, since they are totally different.

Only that the routine you have to inform the PIN of your hardware certificate only works with the type RSACryptoServiceProvider, then you’ll have to adapt it to the type RSACng. I don’t have a hardware certificate here to test, but I wrote a routine for you to test.

First the function LerDispositivo() (kept the name you used, but now you pass directly the certificate X509Certificate2 for her) forehead to see what kind of RSA received from the certificate, if it is CNG (Cryptography API: Next Generation) or CSP (Cryptographic Service Provider), and then redirects to the function that knows how to handle each case:

using System.Security;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Text;

public static RSA LerDispositivo(X509Certificate2 cert, string pin)
{
   // Obtém a chave privada RSA do certificado.
   RSA rsa = cert.GetRSAPrivateKey();

   // Se a chave for do tipo CNG chama a função que trata desse tipo.
   RSACng rsaCng = rsa as RSACng;
   if (rsaCng != null)
   {
      return LerDispositivoCng(rsaCng, pin);
   }

   // Se a chave for do tipo CSP chama a função que trata desse tipo.
   RSACryptoServiceProvider rsaCsp = rsa as RSACryptoServiceProvider;
   if (rsaCsp != null)
   {
      return LerDispositivoCsp(rsaCsp, pin);
   }

   // A chave não era nem CNG nem CSP.
   return null;
}

private static RSA LerDispositivoCng(RSACng rsaCng, string pin)
{
   byte[] propValue;

   // O valor da propriedade deve ser informado como um array de bytes em
   // formato Unicode, e deve contar um caractere nulo como terminador.
   if (pin[pin.Length - 1] == '\0')
   {
      propValue = Encoding.Unicode.GetBytes(pin);
   }
   else
   {
      propValue = new byte[Encoding.Unicode.GetByteCount(pin) + 2];
      Encoding.Unicode.GetBytes(pin, 0, pin.Length, propValue, 0);
   }

   const string NCRYPT_PIN_PROPERTY = "SmartCardPin";

   CngProperty prop = new CngProperty(NCRYPT_PIN_PROPERTY,
                                      propValue,
                                      CngPropertyOptions.None);
   rsaCng.Key.SetProperty(prop);

   return rsaCng;
}

private static RSA LerDispositivoCsp(RSACryptoServiceProvider rsaCsp, string pin)
{
   var securePin = new SecureString();
   foreach (char c in pin)
   {
      securePin.AppendChar(c);
   }

   var cspParams = new CspParameters(
                           rsaCsp.CspKeyContainerInfo.ProviderType,
                           rsaCsp.CspKeyContainerInfo.ProviderName,
                           rsaCsp.CspKeyContainerInfo.KeyContainerName,
                           null,
                           securePin);
   cspParams.KeyNumber = (int) KeyNumber.Signature;
   cspParams.Flags = CspProviderFlags.NoPrompt |
                     CspProviderFlags.UseDefaultKeyContainer;

   return new RSACryptoServiceProvider(cspParams);
}

References:

  • In this line new CspParameters( he returns me Erro CS1729 "CspParameters" não contém um construtor que aceita 5 argumentos

  • Strange, which version of . NET Framework are you using? Anyway, let the constructor of CspParameters only with the 3 first parameters then, and put the securePin right on the property cspParams.KeyPassword like you were doing before. But if your key was CNG-type, he shouldn’t have even been in this routine... At no time did he go through the routine LerDispositivoCng()?

  • I even commented on her, because I checked on debug that he did not enter it, but he returns error in this line: rsaCng.Key.SetProperty(prop); Returning the error: WindowsCryptographicException: Não há suporte para a operação solicitada the. NET Framework version is 4.7

  • Is your A3 type certificate? What type of device?

  • Type A3, I use card.

  • Place a breakpoint on this line that is giving error within the function LerDispositivoCng() and execute the Immediate Window command: ? rsaCng.Key.HasProperty(NCRYPT_PIN_PROPERTY, CngPropertyOptions.None). If you return an error try running the same command again, but with the option CngPropertyOptions.Persist, and tell me what the results were.

  • When I execute the command ? it returns error CS0433: O tipo "CngPropertyOptions" existe em "System.Security.Cryptography.Algorithms, and with the Persist the same error occurs.

  • And this certificate always asks for a PIN when you will use it?

  • Yes, in every note issue, he asks for the PIN, for this reason, I would like him to already pull from the bank.

  • Change the creation of the object CngProperty for the following command: new CngProperty(NCRYPT_PIN_PROPERTY, Encoding.Unicode.GetBytes(pin), CngPropertyOptions.Persist); and see what happens. You made the changes in the function LerDispositivoCsp()? It worked?

  • The same error happens, I tried to add by nuget the reference System.Security.Cryptography.Algorithms I thought it might be this, but I installed it and the same error occurs. No, it does not even enter the LerDispositivoCsp()

  • Mariana, try to change the line NCRYPT_PIN_PROPERTY = "SmartCardPin" for NCRYPT_SECURE_PIN_PROPERTY = "SmartCardSecurePin" and use that new constant NCRYPT_SECURE_PIN_PROPERTY in the creation of the object CngProperty: new CngProperty(NCRYPT_SECURE_PIN_PROPERTY, Encoding.Unicode.GetBytes(pin), CngPropertyOptions.Persist). Also try with the option CngPropertyOptions.None.

  • I did as you helped me but again without success, the same problem occurs.

Show 9 more comments

Browser other questions tagged

You are not signed in. Login or sign up in order to post.