“Tipo di provider non valido specificato” CryptographicException durante il tentativo di caricare la chiave privata del certificato

Sto provando a leggere la chiave privata di un certificato che è stato condiviso con me da un fornitore di servizi di terze parti, quindi posso usarlo per crittografare un codice XML prima di inviarlo via cavo. Lo faccio a livello di programmazione in C #, ma penso che si tratti di un problema di autorizzazioni o di configurazione errata, quindi mi concentrerò sui fatti che sembrano essere più rilevanti:

  • Non penso che questo problema sia legato al codice; il mio codice funziona su altri computer e il problema riguarda il codice di esempio di Microsoft.
  • Il certificato è stato fornito come file PFX ed è solo a scopo di test, quindi include anche un’autorità di certificazione fittizia.
  • Utilizzando MMC.exe, posso importare il certificato nell’archivio personale per il computer locale, prima di concedere le autorizzazioni sulla chiave privata a tutti gli account pertinenti e di trascinare e rilasciare l’autorità di certificazione nelle Autorità di certificazione radice attendibili.
  • Usando C #, posso caricare il certificato (identificato dalla sua impronta digitale) e verificare che abbia una chiave privata usando X509Certificate2.HasPrivateKey . Tuttavia, provare a leggere il tasto causa un errore. In .NET viene generata una CryptographicException con il messaggio “Tipo di provider non valido specificato” quando si tenta di accedere alla proprietà X509Certificate2.PrivateKey . In Win32, chiamando il metodo CryptAcquireCertificatePrivateKey restituisce l’HRESULT equivalente, NTE_BAD_PROV_TYPE .
  • Questa è la stessa eccezione che si verifica anche quando si utilizzano due degli esempi di codice di Microsoft per leggere la chiave privata del certificato.
  • L’installazione dello stesso certificato nell’archivio equivalente per l’utente corrente, invece del computer locale, consente di caricare correttamente la chiave privata.
  • Sono su Windows 8.1 con diritti di amministratore locale e ho provato a eseguire il mio codice sia in modalità normale che elevata. I colleghi di Windows 7 e Windows 8 sono stati in grado di caricare la chiave dall’archivio locale per lo stesso certificato.
  • Posso leggere con successo la chiave privata del certificato di prova IIS autofirmato, che si trova nello stesso punto vendita.
  • Ho già scelto .NET 4.5 (questo errore è stato segnalato con alcune versioni precedenti del framework).
  • Non penso che questo sia un problema con i modelli di certificati, perché mi aspetterei che influenzi allo stesso modo sia la macchina locale sia gli archivi degli utenti correnti?

A differenza dei miei colleghi, ho fatto diversi tentativi precedenti di disinstallare e reinstallare il certificato in vari modi, incluso tramite IIS Manager e includendo anche un vecchio certificato dello stesso emittente. Non riesco a vedere tracce di certificati vecchi o duplicati in MMC. Tuttavia, ho molti file di chiavi private di dimensioni identiche che, in base al tempo dell’ultima scrittura, devono essere rimasti indietro dopo i vari tentativi di installazione. Questi si trovano nelle seguenti posizioni, rispettivamente per la macchina locale e gli attuali archivi utente:

C: \ ProgramData \ Microsoft \ Crypto \ RSA \ MachineKeys

c: \ Users \\ AppData \ Roaming \ Microsoft \ Crypto \ RSA \ S-1-5-21- [rest of user ID]

Quindi, chiunque può consigliare se:

  • È una buona idea disinstallare il certificato utilizzando MMC, eliminare tutti quei file che sembrano chiavi private orfane e quindi reinstallare il certificato e riprovare?
  • Ci sono altri file che dovrei provare a cancellare manualmente?
  • C’è qualcos’altro che dovrei provare?

AGGIORNAMENTO – Aggiunto un esempio di codice che mostra un tentativo di leggere una chiave privata:

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

Ho avuto lo stesso problema su Windows 8 e Server 2012/2012 R2 con due nuovi certificati che ho ricevuto di recente. Su Windows 10, il problema non si verifica più (ma ciò non mi aiuta, poiché il codice che manipola il certificato viene utilizzato su un server). Mentre la soluzione di Joe Strommen funziona in linea di principio, il diverso modello di chiave privata richiederebbe enormi modifiche al codice utilizzando i certificati. Trovo che una soluzione migliore sia convertire la chiave privata da CNG in RSA, come spiegato da Remy Blok qui .

Remy utilizza OpenSSL e due strumenti precedenti per realizzare la conversione della chiave privata, volevamo automatizzarlo e sviluppare una soluzione solo OpenSSL. Dato MYCERT.pfx con password della chiave privata MYPWD in formato CNG, questi sono i passaggi per ottenere un nuovo CONVERTED.pfx con chiave privata in formato RSA e stessa password:

  1. Estrai chiavi pubbliche, catena di certificati completa:

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

  1. Estrai chiave privata:

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

  1. Converti la chiave privata in formato RSA:

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

  1. Unisci le chiavi pubbliche con la chiave privata RSA al nuovo PFX:

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

Se si carica il file pfx convertito o lo si importa nell’archivio certificati di Windows al posto del formato CNG pfx, il problema scompare e il codice C # non deve essere modificato.

Un altro trucchetto che ho riscontrato durante l’automazione di questo: utilizziamo le password generate a lungo per la chiave privata e la password potrebbe contenere “. Per la riga di comando di OpenSSL,” i caratteri all’interno della password devono essere sfuggiti come “”.

Il collegamento al blog di Alejandro è fondamentale.

Credo che questo sia dovuto al fatto che il certificato è memorizzato sul tuo computer con l’API CNG (“Crypto Next-Generation”). La vecchia API .NET non è compatibile con esso, quindi non funziona.

È ansible utilizzare il wrapper Security.Cryptography per questa API ( disponibile su Codeplex ). Questo aggiunge i metodi di estensione a X509Certificate/X509Certificate2 , quindi il tuo codice sarà simile al seguente:

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

Sfortunatamente il modello a oggetti per le chiavi private CNG è abbastanza diverso. Non sono sicuro che tu possa esportarli in XML come nell’esempio di codice originale … nel mio caso ho solo dovuto firmare alcuni dati con la chiave privata.

Ecco un’altra ragione per cui questo può accadere, questo era un problema strano e dopo aver lottato per un giorno ho risolto il problema. Come esperimento ho cambiato il permesso per la cartella “C: \ ProgramData \ Microsoft \ Crypto \ RSA \ MachineKeys” che contiene i dati della chiave privata per i certificati che utilizzano l’archivio chiavi della macchina. Quando si modifica l’authorization per questa cartella, tutti i privatekeys vengono visualizzati come “Provider KSP Software Microsoft” che non è il provider (nel mio caso si suppone che siano “Provider Microsoft Schannel Cryptographic Microsoft”).

Soluzione: ripristinare le autorizzazioni sulla cartella Machinekeys

Il permesso originale per questa cartella può essere trovato qui . Nel mio caso ho cambiato il permesso per “Everyone”, ho dato i permessi di lettura in cui è stato rimosso il segno di spunta “Permessi speciali”. Così ho controllato con uno dei membri del mio team (cartella clic destro> Proprietà> Sicurezza> Avanzate> seleziona “Tutti”> Modifica> Fai clic su “Impostazioni avanzate” nell’elenco delle caselle di controllo dei permessi

Permessi speciali

Spero che questo salvi la giornata di qualcuno!

Qui è dove ho trovato la fonte della risposta, il merito va a lui per averlo documentato.

Ho anche avuto questo problema e dopo aver tentato i suggerimenti in questo post senza successo. Sono stato in grado di risolvere il problema ricaricando il certificato con l’utilità di certificazione Digicert https://www.digicert.com/util/ . Ciò consente di selezionare il provider in cui caricare il certificato. Nel mio caso, il caricamento del certificato nel provider Microsoft Schannel Cryptographic Provider in cui mi aspettavo che fosse, in primo luogo, ha risolto il problema.

Versione PowerShell della risposta da @ berend-engelbrecht, assumendo openssl installato via 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 

Come hanno sottolineato molte altre risposte, questo problema si verifica quando la chiave privata è una chiave Windows Cryptography: Next Generation (CNG) anziché una chiave “classica” API Cryptographic API (CAPI).

A partire da .NET Framework 4.6 è ansible accedere alla chiave privata (supponendo che sia una chiave RSA) tramite un metodo di estensione su X509Certificate2: cert.GetRSAPrivateKey() .

Quando la chiave privata è detenuta da CNG, il metodo di estensione GetRSAPrivateKey restituirà un object RSACng (nuovo al framework in 4.6). Poiché CNG dispone di un pass-through per leggere le chiavi software CAPI precedenti, GetRSAPrivateKey solito restituirà un RSACng anche per una chiave CAPI; ma se CNG non è in grado di caricarlo (ad esempio è una chiave HSM senza driver CNG) allora GetRSAPrivateKey restituirà un RSACryptoServiceProvider .

Si noti che il tipo di GetRSAPrivateKey per GetRSAPrivateKey è RSA . A partire da .NET Framework v4.6 non è necessario eseguire il cast oltre RSA per le operazioni standard; l’unico motivo per utilizzare RSACng o RSACryptoServiceProvider è quando è necessario eseguire l’interoperabilità con programmi o librerie che utilizzano NCRYPT_KEY_HANDLE o l’identificatore della chiave (o l’apertura di una chiave persistente per nome). (.NET Framework v4.6 aveva molti posti che ancora lanciavano l’object di input a RSACryptoServiceProvider , ma quelli furono tutti eliminati dal 4.6.2 (ovviamente, questo è più di 2 anni fa a questo punto)).

Il supporto del certificato ECDSA è stato aggiunto in 4.6.1 tramite un metodo di estensione GetECDsaPrivateKey e DSA è stato aggiornato in 4.6.2 tramite GetDSAPrivateKey .

Su .NET Core il valore restituito da Get[Algorithm]PrivateKey cambia a seconda del sistema operativo. Per RSA è RSACng / RSACryptoServiceProvider su Windows, RSAOpenSsl su Linux (o qualsiasi OS simile a UNIX tranne macOS) e un tipo non pubblico su macOS (che significa che non puoi lanciarlo al di fuori di RSA ).