manager installed computer certificates c# windows certificate mmc certificatestore

c# - installed - CryptographicException “tipo de proveedor no válido” cuando se intenta cargar la clave privada del certificado



windows certificate manager (6)

Estoy tratando de leer la clave privada de un certificado que ha sido compartido conmigo por un proveedor de servicios externo, por lo que puedo usarlo para cifrar algunos XML antes de enviarlos a través del cable. Lo estoy haciendo de manera programática en C #, pero creo que este es un problema de permisos o mala configuración, por lo que me centraré en los hechos que parecen ser más relevantes:

  • No creo que este problema esté relacionado con el código; mi código funciona en otras computadoras y el problema afecta al código de muestra de Microsoft.
  • El certificado se proporcionó como un archivo PFX y es solo para fines de prueba, por lo que también incluye una autoridad de certificación ficticia.
  • Con MMC.exe, puedo importar el certificado en el almacén personal de la máquina local, antes de conceder permisos en la clave privada a todas las cuentas relevantes, y arrastrar y soltar la autoridad de certificación en las Autoridades de certificación de raíz de confianza.
  • Utilizando C #, puedo cargar el certificado (identificado por su huella digital) y verificar que tenga una clave privada usando X509Certificate2.HasPrivateKey . Sin embargo, intentar leer la clave provoca un error. En .NET se lanza una CryptographicException con el mensaje "Tipo de proveedor no válido especificado" al intentar acceder a la propiedad X509Certificate2.PrivateKey . En Win32, llamar al método CryptAcquireCertificatePrivateKey devuelve el HRESULT equivalente, NTE_BAD_PROV_TYPE .
  • Esta es la misma excepción que también ocurre cuando se usan dos de los ejemplos de código propios de Microsoft para leer la clave privada del certificado.
  • La instalación del mismo certificado en el almacén equivalente para el usuario actual, en lugar de la máquina local, permite que la clave privada se cargue con éxito.
  • Estoy en Windows 8.1 con derechos de administrador local, y he intentado ejecutar mi código en modo normal y elevado. Los colegas en Windows 7 y Windows 8 han podido cargar la clave desde el almacén de la máquina local para el mismo certificado.
  • Puedo leer con éxito la clave privada del certificado de prueba IIS autofirmado, que se encuentra en la misma ubicación de la tienda.
  • Ya estoy apuntando a .NET 4.5 (este error se ha informado con algunas versiones anteriores del marco).
  • No creo que este sea un problema con las plantillas de certificado, porque espero que eso afecte tanto a la máquina local como a las tiendas de usuarios actuales.

A diferencia de mis colegas, he realizado varios intentos previos para desinstalar y reinstalar el certificado de varias maneras, incluso a través del Administrador de IIS y también un certificado anterior del mismo emisor. No puedo ver ningún rastro de certificados antiguos o duplicados en MMC. Sin embargo, tengo muchos archivos de clave privada de tamaño idéntico que, según la última hora de escritura, se deben haber dejado atrás después de varios intentos de instalación. Estos se encuentran en las siguientes ubicaciones, para la máquina local y los almacenes de usuarios actuales, respectivamente:

c: / ProgramData / Microsoft / Crypto / RSA / MachineKeys

c: / Users // AppData / Roaming / Microsoft / Crypto / RSA / S-1-5-21- [resto de ID de usuario]

Entonces, ¿alguien puede avisar si:

  • Es una buena idea desinstalar el certificado utilizando MMC, eliminar todos los archivos que parecen claves privadas huérfanas y luego volver a instalar el certificado e intentarlo de nuevo.
  • ¿Hay otros archivos que debería intentar eliminar manualmente?
  • ¿Hay algo más que deba intentar?

ACTUALIZACIÓN: se agregó un ejemplo de código que muestra un intento de leer una clave privada:

static void Main() { // Exception occurs when trying to read the private key after loading certificate from here: X509Store store = new X509Store("MY", StoreLocation.LocalMachine); // Exception does not occur if certificate was installed to, and loaded from, here: //X509Store store = new X509Store("MY", StoreLocation.CurrentUser); store.Open(OpenFlags.ReadOnly | OpenFlags.OpenExistingOnly); X509Certificate2Collection collection = (X509Certificate2Collection)store.Certificates; X509Certificate2Collection fcollection = (X509Certificate2Collection)collection.Find(X509FindType.FindByTimeValid, DateTime.Now, false); X509Certificate2Collection scollection = X509Certificate2UI.SelectFromCollection(fcollection, "Test Certificate Select", "Select a certificate from the following list to get information on that certificate", X509SelectionFlag.MultiSelection); Console.WriteLine("Number of certificates: {0}{1}", scollection.Count, Environment.NewLine); foreach (X509Certificate2 x509 in scollection) { try { Console.WriteLine("Private Key: {0}", x509.HasPrivateKey ? x509.PrivateKey.ToXmlString(false) : "[N/A]"); x509.Reset(); } catch (CryptographicException ex) { Console.WriteLine(ex.Message); } } store.Close(); Console.ReadLine(); }


Aquí hay otra razón por la que esto puede suceder, fue un problema extraño y después de luchar por un día resolví el problema. Como experimento, cambié el permiso para la carpeta "C: / ProgramData / Microsoft / Crypto / RSA / MachineKeys" que contiene datos de clave privada para certificados usando el almacén de claves de la máquina. Cuando cambia el permiso para esta carpeta, todas las claves privadas aparecen como "Proveedor de software KSP de Microsoft", que no es el proveedor (en mi caso, se supone que son "Proveedor RSA Schannel Cryptographic Provider" de Microsoft).

Solución: Restablecer permisos a la carpeta Machinekeys

El permiso original para esta carpeta se puede encontrar here . En mi caso, cambié el permiso para "Todos", le di permisos de lectura donde eliminó la marca de "Permisos especiales". Así que verifiqué con uno de los miembros de mi equipo (Carpeta de clic derecho> Propiedades> Seguridad> Avanzada> seleccione "Todos"> Editar> Haga clic en "Configuración avanzada" en la lista de casillas de verificación de permisos

Espero que esto le salve el día a alguien!

Aquí es donde encontré la source la respuesta, el crédito es para él por documentar esto.


Como muchas otras respuestas han señalado, este problema surge cuando la clave privada es una clave de Criptografía de Windows: Próxima Generación (CNG) en lugar de una clave "clásica" de la API Criptográfica de Windows (CAPI).

A partir de .NET Framework 4.6, se puede acceder a la clave privada (asumiendo que es una clave RSA) a través de un método de extensión en X509Certificate2: cert.GetRSAPrivateKey() .

Cuando CNG mantiene la clave privada, el método de extensión GetRSAPrivateKey devolverá un objeto RSACng (nuevo en el marco en 4.6). Debido a que CNG tiene un paso para leer claves de software CAPI más antiguas, GetRSAPrivateKey generalmente devolverá un RSACng incluso para una clave CAPI; pero si CNG no puede cargarlo (por ejemplo, es una clave HSM sin controlador CNG), GetRSAPrivateKey devolverá un RSACryptoServiceProvider .

Tenga en cuenta que el tipo de retorno para GetRSAPrivateKey es RSA . A partir de .NET Framework v4.6, no debería tener que ir más allá de RSA para operaciones estándar; la única razón para usar RSACng o RSACryptoServiceProvider es cuando necesita interoperar con programas o bibliotecas que usan NCRYPT_KEY_HANDLE o el identificador de clave (o abrir una clave persistente por nombre). (.NET Framework v4.6 tenía muchos lugares que aún RSACryptoServiceProvider objeto de entrada a RSACryptoServiceProvider , pero todos fueron eliminados por 4.6.2 (por supuesto, eso fue hace más de 2 años)).

La compatibilidad con el certificado ECDSA se agregó en 4.6.1 a través del método de extensión GetECDsaPrivateKey , y la DSA se actualizó en 4.6.2 a través de GetDSAPrivateKey .

En .NET Core, el valor de retorno de Get[Algorithm]PrivateKey cambia según el sistema operativo. Para RSA es RSACng / RSACryptoServiceProvider en Windows, RSAOpenSsl en Linux (o en cualquier sistema operativo similar a UNIX, excepto macOS), y un tipo no público en macOS (lo que significa que no puede hacerlo más allá de RSA ).


El enlace al blogs.msdn.com/b/alejacma/archive/2009/12/22/… es clave.

Creo que esto se debe a que el certificado se almacena en su máquina con la API de CNG ("Crypto Next-Generation"). La antigua API de .NET no es compatible con ella, por lo que no funciona.

Puede usar la envoltura Security.Cryptography para esta API ( disponible en Codeplex ). Esto agrega métodos de extensión a X509Certificate/X509Certificate2 , por lo que su código tendrá un aspecto similar al siguiente:

using Security.Cryptography.X509Certificates; // Get extension methods X509Certificate cert; // Populate from somewhere else... if (cert.HasCngKey()) { var privateKey = cert.GetCngPrivateKey(); } else { var privateKey = cert.PrivateKey; }

Desafortunadamente, el modelo de objeto para las claves privadas de CNG es un poco diferente. No estoy seguro de si puede exportarlos a XML como en su ejemplo de código original ... en mi caso, solo necesitaba firmar algunos datos con la clave privada.


También tuve este problema y después de intentar las sugerencias en esta publicación sin éxito. Pude resolver mi problema al volver a cargar el certificado con la utilidad de certificados Digicert https://www.digicert.com/util/ . Esto permite que uno seleccione el proveedor para cargar el certificado en. En mi caso, al cargar el certificado en el proveedor de servicios criptográficos Schannel de RSA de Microsoft donde esperaba que estuviera, en primer lugar, se resolvió el problema.


Tuve el mismo problema en Windows 8 y Server 2012/2012 R2 con dos nuevos certificados que recibí recientemente. En Windows 10, el problema ya no ocurre (pero eso no me ayuda, ya que el código que manipula el certificado se usa en un servidor). Si bien la solución de Joe Strommen en principio funciona, el modelo de clave privada diferente requeriría un cambio masivo en el código utilizando los certificados. Me parece que una mejor solución es convertir la clave privada de CNG a RSA, como lo explica Remy Blok here .

Remy utiliza OpenSSL y dos herramientas anteriores para realizar la conversión de la clave privada. Queríamos automatizarlo y desarrollar una solución exclusiva para OpenSSL. Dado MYCERT.pfx con contraseña de clave privada MYPWD en formato CNG, estos son los pasos para obtener un nuevo CONVERTED.pfx con clave privada en formato RSA y la misma contraseña:

  1. Extraer claves públicas, cadena de certificados completa:

OpenSSL pkcs12 -in "MYCERT.pfx" -nokeys -out "MYCERT.cer" -passin "pass:MYPWD"

  1. Extraer clave privada:

OpenSSL pkcs12 -in "MYCERT.pfx" -nocerts –out “MYCERT.pem" -passin "pass:MYPWD" -passout "pass:MYPWD"

  1. Convertir la clave privada al formato RSA:

OpenSSL rsa -inform PEM -in "MYCERT.pem" -out "MYCERT.rsa" -passin "pass:MYPWD" -passout "pass:MYPWD"

  1. Combine las claves públicas con la clave privada RSA al nuevo PFX:

OpenSSL pkcs12 -export -in "MYCERT.cer" -inkey "MYCERT.rsa" -out "CONVERTED.pfx" -passin "pass:MYPWD" -passout "pass:MYPWD"

Si carga el pfx convertido o lo importa en el almacén de certificados de Windows en lugar del pfx en formato CNG, el problema desaparece y el código C # no necesita cambiarse.

Un error adicional que encontré al automatizar esto: usamos contraseñas generadas durante mucho tiempo para la clave privada y la contraseña puede contener ". Para la línea de comandos de OpenSSL," los caracteres dentro de la contraseña deben escaparse como "".


Versión de Powershell de la respuesta de @ berend-engelbrecht, asumiendo que openssl instaló vía chocolatey

function Fix-Certificates($certPasswordPlain) { $certs = Get-ChildItem -path "*.pfx" -Exclude "*.converted.pfx" $certs | ForEach-Object{ $certFile = $_ $shortName = [io.path]::GetFileNameWithoutExtension($certFile.Name) Write-Host "Importing $shortName" $finalPfx = "$shortName.converted.pfx" Set-Alias openssl "C:/Program Files/OpenSSL/bin/openssl.exe" # Extract public key OpenSSL pkcs12 -in $certFile.Fullname -nokeys -out "$shortName.cer" -passin "pass:$certPasswordPlain" # Extract private key OpenSSL pkcs12 -in $certFile.Fullname -nocerts -out "$shortName.pem" -passin "pass:$certPasswordPlain" -passout "pass:$certPasswordPlain" # Convert private key to RSA format OpenSSL rsa -inform PEM -in "$shortName.pem" -out "$shortName.rsa" -passin "pass:$certPasswordPlain" -passout "pass:$certPasswordPlain" 2>$null # Merge public keys with RSA private key to new PFX OpenSSL pkcs12 -export -in "$shortName.cer" -inkey "$shortName.rsa" -out $finalPfx -passin "pass:$certPasswordPlain" -passout "pass:$certPasswordPlain" # Clean up Remove-Item "$shortName.pem" Remove-Item "$shortName.cer" Remove-Item "$shortName.rsa" Write-Host "$finalPfx created" } } # Execute in cert folder Fix-Certificates password