Benutzer mit den meisten Antworten
Signieren mit SHA256: Datei mit einem Zertifikat aus dem Zertifikatsspeicher signieren

Frage
-
Hallo zusammen,
ich hab es, ohne Erfolg, schon in andren Forum versucht.
Ich habe den Hash einer Datei und möchte diesen mit einem Zertifikat aus dem Zertifikatsspeicher signieren.
Den Hash (Byte Array) und das Zertifikat (X509Certificate2) bekomme ich übergeben.
Versucht habe ich es mit folgendem Code:
// http://blogs.msdn.com/b/shawnfa/archive/2008/08/25/using-rsacryptoserviceprovider-for-rsa-sha256-signatures.aspx RSACryptoServiceProvider rsa_msdn = (RSACryptoServiceProvider)cert.PrivateKey; byte[] signature_msdn = rsa_msdn.SignData(data, "SHA256"); if (rsa_msdn.VerifyData(data, "SHA256", signature_msdn)) { Console.WriteLine("RSA-SHA256 signature verified"); Console.WriteLine(ConvertHexToString(signature_msdn)); } else { Console.WriteLine("RSA-SHA256 signature failed to verify"); Console.WriteLine(ConvertHexToString(signature_msdn)); } RSACryptoServiceProvider rsa_msdn2 = (RSACryptoServiceProvider)cert.PrivateKey; byte[] signature_msdn2 = rsa_msdn2.SignData(data, "SHA256"); if (rsa_msdn2.VerifyData(data, "SHA256", signature_msdn2)) { Console.WriteLine("RSA-SHA256 signature verified"); Console.WriteLine(ConvertHexToString(signature_msdn2)); } else { Console.WriteLine("RSA-SHA256 signature failed to verify"); Console.WriteLine(ConvertHexToString(signature_msdn2)); }
Hier bekomme ich beim 'SignData(data, "SHA256")' den Fehler 'Ungültiger Algorithmus angegeben':
Unbehandelte Ausnahme: System.Security.Cryptography.CryptographicException: Ungültiger Algorithmus angegeben bei System.Security.Cryptography.CryptographicException.ThrowCryptogaphicException(Int32 hr) bei System.Security.Cryptography.Utils._SignValue(SafeKeyHandle hKey, Int32 keyNumber, Int32 calgKey, Int32 calgHash, Byte[] hash, Int32 dwFlags) bei System.Security.Cryptography.RSACryptoServiceProvider.SignHash(Byte[] rgbHash, String str) bei System.Security.Cryptography.RSACryptoServiceProvider.SignData(Byte[] buffer, Object halg) bei PlainSignature.PlainSignature.SignPfx(Byte[] data, X509Certificate2 cert) in D:\PlainSignature\PlainSignature.cs:Zeile 741. bei PlainSignature.PlainSignature.sign_Hash_RSA_SHA256_Certificate(Byte[] hashValue, String subjectCommonName, String outputFile) in D:\PlainSignature\PlainSignature.cs:Zeile 416. bei PlainSignature.PlainSignature.Main(String[] args) in D:\PlainSignature\PlainSignature.cs:Zeile 162.
Wenn ich anstelle des SHA256 einen SHA1 verwende funktioniert alles bestens.
Kommt der Key nicht aus dem Store dann klappt es auch mit SHA256:RSACryptoServiceProvider rsa_msdn = new RSACryptoServiceProvider(); byte[] signature_msdn = rsa_msdn.SignData(data, "SHA256"); if (rsa_msdn.VerifyData(data, "SHA256", signature_msdn)) { Console.WriteLine("RSA-SHA256 signature verified"); Console.WriteLine(ConvertHexToString(signature_msdn)); } else { Console.WriteLine("RSA-SHA256 signature failed to verify"); Console.WriteLine(ConvertHexToString(signature_msdn)); }
Kurz noch zu meiner Umgbung:
Ich verwende Visual Studio 2008 auf einem Windows 7 Rechner.
Dort sind folgende Frameworks installiert:
Microsoft .NET Compact Framework 2.0 SP2
Microsoft .NET Compact Framework 3.5 (In der Registry ist Value bei 'SP' auf '1' gesetzt)
Microsoft .NET Framework 4 Client Profile
Microsoft .NET Framework 4 Client Profile DEU Language Pack
Microsoft .NET Framework 4 Extended
Microsoft .NET FRamework 4 Extended DEU Language Pack
Microsoft .NET Framework 4 Multi-Targeting Pack
Hat jemand eine Idee wie ich das Zertifikat für die Signatur verwenden kann?
Aendrew
Antworten
-
Hallo Aendrew,
Werte doch rsa_msdn.CspKeyContainerInfo.ProviderType für beide o.g. Szenarien aus. Du wirst wahrscheinlich folgendes entdecken: SHA256 funktioniert nur mit ProviderType 24 (PROV_RSA_AES), d.h. wenn Du denn CSP wie folgt erzeugst (ab Windows Server 2003):
RSACryptoServiceProvider rsa_msdn = new RSACryptoServiceProvider();
oder:
CspParameters cspParams = new CspParameters(24); RSACryptoServiceProvider rsa_msdn = new RSACryptoServiceProvider(cspParams);
Hat der Provider jedoch den ProviderTyp 1 (PROV_RSA_FULL) unterstützt er die Signierung über SHA256 einfach nicht. Du kannst nicht mit einem Algorithmus signieren, der vom Provider nicht unterstützt wird.
Erstelle Dein Zertifikat mit einem Provider vom Typ PROV_RSA_AES oder bitte den Aussteller das für Dich zu tun.
Für Provider-Typen siehe z.B.: CspParameters.ProviderType-Feld
http://msdn.microsoft.com/de-de/library/system.security.cryptography.cspparameters.providertype(v=VS.100).aspxGruß
Marcel- Als Antwort vorgeschlagen Robert BreitenhoferModerator Montag, 25. Juli 2011 15:32
- Als Antwort markiert Robert BreitenhoferModerator Mittwoch, 3. August 2011 09:53
-
Hallo Aendrew,
> Aber wie bekomme ich dann den privaten Key in den CryptoServiceProvider ohne ihn neu erzeugen zu lassen? Es muss doch die Möglichkeit geben den CSP zu wählen und das vorhandene Zertifikat zu verwenden, oder?
Wenn ein CSP einen Algorithmus nicht unterstützt, ist - wie bereits erwähnt - nichts zu machen.
Du kannst aber die CSP-Parameter aus dem nicht unterstützenden CSP exportieren, einen neuen CSP erstellen (der SHA256 unterstützt) und die CSP-Parameter neu importieren.
Hier ein kleines Beispiel dazu:using System; using System.IO; using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; namespace FileSignatureTool { class Program { static void Main(string[] args) { if (args.Length != 3) { PrintUsing(); } else { string option = args[0]; string sourceFile = args[1]; string signatureFile = args[2]; byte[] inputFileBytes = File.ReadAllBytes(sourceFile); X509Certificate2 selectedCertificate = SelectCertificateFromDialog(); if (selectedCertificate != null) { RSACryptoServiceProvider rsaCSP = (RSACryptoServiceProvider)selectedCertificate.PrivateKey; RSAParameters rsaParameters = rsaCSP.ExportParameters(true); CspParameters cspParameters = new CspParameters(24); RSACryptoServiceProvider rsaAesCSP = new RSACryptoServiceProvider(cspParameters); rsaAesCSP.ImportParameters(rsaParameters); HashAlgorithm hashAlgorithm = HashAlgorithm.Create("SHA256"); if (option.ToLower() == "-sign") { // This line throws exception 'Key not valid for use in specified state' // if the private key is not marked as exportable. byte[] signedBytes = rsaAesCSP.SignData(inputFileBytes, hashAlgorithm); File.WriteAllBytes(signatureFile, signedBytes); Console.WriteLine("Die Signatur wurde erfolgreich gespeichert."); } else if (option.ToLower() == "-verify") { byte[] signedHashBytes = File.ReadAllBytes(signatureFile); bool isValid = rsaAesCSP.VerifyData(inputFileBytes, hashAlgorithm, signedHashBytes); Console.WriteLine("Signatur ist gültig: {0}", isValid ? "JA" : "NEIN"); } else { PrintUsing(); } } } Console.WriteLine("\n Drücken Sie eine beliebige Taste, um das Programm zu beenden."); Console.ReadKey(true); } private static void PrintUsing() { Console.WriteLine("\nVerwendung: FileSignatureTool.exe [-Option] [Quelldatei] [Signaturdatei]"); Console.WriteLine("\nOption:"); Console.WriteLine("\n-sign\tErstellt eine Signaturdatei"); Console.WriteLine("-verify\tVerifiziert die Signatur"); } private static X509Certificate2 SelectCertificateFromDialog() { X509Store myCurrentUserStore = new X509Store(StoreName.My, StoreLocation.CurrentUser); myCurrentUserStore.Open(OpenFlags.ReadOnly); X509Certificate2Collection certificateCollection = myCurrentUserStore.Certificates; X509Certificate2 selectedCertificate = null; X509Certificate2Collection selectedCertificateCollection = X509Certificate2UI.SelectFromCollection(certificateCollection, "Zertifikate", "Bitte wählen Sie ein Zertifikat zum Signieren aus", X509SelectionFlag.SingleSelection); if (selectedCertificateCollection.Count > 0) { X509Certificate2Enumerator en = selectedCertificateCollection.GetEnumerator(); en.MoveNext(); selectedCertificate = en.Current; } myCurrentUserStore.Close(); return selectedCertificate; } } }
Wenn Dein Ziel hingegen der ist, eine ASN.1-Datei zu erzeugen (CMS/PKCS# 7), welche die Daten signiert und wrappt (eingebettete Signatur), kannst Du einfach den Weg über CmsSigner und SignedCms nehmen:using System; using System.IO; using System.Security.Cryptography; using System.Security.Cryptography.Pkcs; using System.Security.Cryptography.X509Certificates; namespace SignFile { class Program { static void Main(string[] args) { if (args.Length != 3) { PrintUsing(); } else { string option = args[0]; string sourceFile = args[1]; string targetFile = args[2]; byte[] bytesToOperateOn = File.ReadAllBytes(sourceFile); if (option.ToLower() == "-sign") { X509Certificate2 selectedCertificate = SelectCertificateFromDialog(); if (selectedCertificate != null) { CmsSigner signer = GetSha256Signer(selectedCertificate); byte[] encodedAndSignedCmsBytes = SignAndEncodeBytes(bytesToOperateOn, signer); File.WriteAllBytes(targetFile, encodedAndSignedCmsBytes); Console.WriteLine("Die Datei wurde erfolgreich signiert."); } } else if (option.ToLower() == "-verifysave") { SignedCms cms = new SignedCms(new ContentInfo(bytesToOperateOn), false); try { cms.Decode(bytesToOperateOn); try { cms.CheckSignature(true); File.WriteAllBytes(targetFile, cms.ContentInfo.Content); Console.WriteLine("Die Signatur wurde erfolgreich überprüft."); } catch (CryptographicException cex) { Console.WriteLine("Signatur-Überprüfung fehlgeschlagen: {0}", cex.Message); } } catch (CryptographicException ex) { Console.WriteLine("Fehler beim Dekodieren der signierten Nachricht: {0}", ex.Message); } } else { PrintUsing(); } } Console.WriteLine("\n Drücken Sie eine beliebige Taste, um das Programm zu beenden."); Console.ReadKey(true); } private static void PrintUsing() { Console.WriteLine("\nVerwendung: SignFile.exe [-Option] [Quelldatei] [Zieldatei]"); Console.WriteLine("\nOption:"); Console.WriteLine("\n-sign\tSigniert und Kodiert die Datei"); Console.WriteLine("-verifysave\tVerifiziert die Signatur und speichert den\n\tdekodierten Inhalt der Quelldatei in die Zieldatei ab"); } private static byte[] SignAndEncodeBytes(byte[] bytesToSign, CmsSigner signer) { SignedCms cms = new SignedCms(new ContentInfo(bytesToSign), false); cms.ComputeSignature(signer, true); return cms.Encode(); } private static CmsSigner GetSha256Signer(X509Certificate2 selectedCertificate) { CmsSigner signer = new CmsSigner(selectedCertificate); Oid oid = new Oid("SHA256"); signer.DigestAlgorithm = oid; return signer; } private static X509Certificate2 SelectCertificateFromDialog() { X509Store myCurrentUserStore = new X509Store(StoreName.My, StoreLocation.CurrentUser); myCurrentUserStore.Open(OpenFlags.ReadOnly); X509Certificate2Collection certificateCollection = myCurrentUserStore.Certificates; X509Certificate2 selectedCertificate = null; X509Certificate2Collection selectedCertificateCollection = X509Certificate2UI.SelectFromCollection(certificateCollection, "Zertifikate", "Bitte wählen Sie ein Zertifikat zum Signieren aus", X509SelectionFlag.SingleSelection); if (selectedCertificateCollection.Count > 0) { X509Certificate2Enumerator en = selectedCertificateCollection.GetEnumerator(); en.MoveNext(); selectedCertificate = en.Current; } myCurrentUserStore.Close(); return selectedCertificate; } } }
Gruß
Marcel- Als Antwort vorgeschlagen Elmar BoyeEditor Mittwoch, 27. Juli 2011 08:35
- Als Antwort markiert Robert BreitenhoferModerator Dienstag, 2. August 2011 15:25
-
Hallo Aendrew,
> Ob mein Problem dadurch wirklich gelöst ist muss ich erst noch prüfen [...]
Mein erstes Beispiel ging - wie Du richtig bemerkt hast - von einem exportierbaren Schlüssel aus. Wenn der Schlüssel nun beim Import als nicht-exportierbar gekennzeichnet wurde, muss man vom diesem Export/Import-Manöver freilich absehen.
Stattdessen kann man die CspParameters, die zur Erzeugung des neuen RSACryptoServiceProvider verwendet werden, so einstellen, dass sie auf den selben Container verweisen wie der ursprüngliche CSP, d.h.: ((RSACryptoServiceProvider) selectedCertificate.PrivateKey).CspContainerInfo.KeyContainerName.
Hier also das im Lichte der neuen Anforderungen umgearbeitete, funktionierende Beispiel, das auf Schlüsselexport verzichten kann:using System; using System.IO; using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; namespace FileSignatureTool { class Program { static void Main(string[] args) { if (args.Length != 3) { PrintUsing(); } else { string option = args[0]; string sourceFile = args[1]; string signatureFile = args[2]; byte[] inputFileBytes = File.ReadAllBytes(sourceFile); X509Certificate2 selectedCertificate = SelectCertificateFromDialog(); if (selectedCertificate != null) { RSACryptoServiceProvider rsaCSP = (RSACryptoServiceProvider)selectedCertificate.PrivateKey; // Avoiding export here due to having to use an unexportable key. // RSAParameters rsaParameters = rsaCSP.ExportParameters(true); CspParameters cspParameters = new CspParameters(); cspParameters.KeyContainerName = rsaCSP.CspKeyContainerInfo.KeyContainerName; cspParameters.KeyNumber = rsaCSP.CspKeyContainerInfo.KeyNumber == KeyNumber.Exchange ? 1 : 2; // As of .NET Framework 3.5 SP1 the default CSP has changed from PROV_RSA_FULL (1) to // PROV_RSA_AES (24) for all OS > XP, so default is 24 even without setting // CspParameters.ProviderType to 24 RSACryptoServiceProvider rsaAesCSP = new RSACryptoServiceProvider(cspParameters); rsaAesCSP.PersistKeyInCsp = false; // We have set KeyContainerName to an existing container, no need to import parameters // rsaAesCSP.ImportParameters(rsaParameters); HashAlgorithm hashAlgorithm = HashAlgorithm.Create("SHA256"); if (option.ToLower() == "-sign") { // This line throws exception 'Key not valid for use in specified state' // if the private key is not marked as exportable and we if we also have // not set the KeyContainerName to the container of the store certificate byte[] signedBytes = rsaAesCSP.SignData(inputFileBytes, hashAlgorithm); File.WriteAllBytes(signatureFile, signedBytes); Console.WriteLine("Die Signatur wurde erfolgreich gespeichert."); } else if (option.ToLower() == "-verify") { byte[] signedHashBytes = File.ReadAllBytes(signatureFile); bool isValid = rsaAesCSP.VerifyData(inputFileBytes, hashAlgorithm, signedHashBytes); Console.WriteLine("Signatur ist gültig: {0}", isValid ? "JA" : "NEIN"); } else { PrintUsing(); } } } Console.WriteLine("\n Drücken Sie eine beliebige Taste, um das Programm zu beenden."); Console.ReadKey(true); } private static void PrintUsing() { Console.WriteLine("\nVerwendung: FileSignatureTool.exe [-Option] [Quelldatei] [Signaturdatei]"); Console.WriteLine("\nOption:"); Console.WriteLine("\n-sign\tErstellt eine Signaturdatei"); Console.WriteLine("-verify\tVerifiziert die Signatur"); } private static X509Certificate2 SelectCertificateFromDialog() { X509Store myCurrentUserStore = new X509Store(StoreName.My, StoreLocation.CurrentUser); myCurrentUserStore.Open(OpenFlags.ReadOnly); X509Certificate2Collection certificateCollection = myCurrentUserStore.Certificates; X509Certificate2 selectedCertificate = null; X509Certificate2Collection selectedCertificateCollection = X509Certificate2UI.SelectFromCollection(certificateCollection, "Zertifikate", "Bitte wählen Sie ein Zertifikat zum Signieren aus", X509SelectionFlag.SingleSelection); if (selectedCertificateCollection.Count > 0) { X509Certificate2Enumerator en = selectedCertificateCollection.GetEnumerator(); en.MoveNext(); selectedCertificate = en.Current; } myCurrentUserStore.Close(); return selectedCertificate; } } }
Zur Sicherheit noch: Das Markieren eines Schlüssels als nicht exportierbar stellt keine reale security boundary dar. Es gibt genügend Tools wie jailbreak etc., die den Export trotz Markierung ermöglichen. Wie man im unten verlinkten Windows PKI Blogartikel lesen kann wird beim Markieren einfach im Key-Storage ein Flag gesetzt (NCRYPT_ALLOW_EXPORT_FLAG), das Zertifikat selber weiß nichts davon.Windows PKI blog - Marking private keys as non-exportable with certutil -importpfx:
http://blogs.technet.com/b/pki/archive/2007/07/29/marking-private-keys-as-non-exportable-with-certutil-importpfx.aspxKey Storage Property Identifiers:
http://msdn.microsoft.com/en-us/library/aa376242.aspxGruß
Marcel- Bearbeitet Marcel RomaModerator Mittwoch, 27. Juli 2011 21:24 Code-Beispiel hinzugefügt
- Als Antwort vorgeschlagen Marcel RomaModerator Montag, 1. August 2011 14:37
- Als Antwort markiert Aendrew Dienstag, 2. August 2011 15:08
-
Hallo Aendrew,
> funktioniert unter Windows 7 bestens. Leider klappt es unter Windows XP nicht [...] scheinbar gibts hier noch Probleme.
Was meinst Du genau mit "klappt es unter Windows XP nicht"? - Welche Fehlermeldung erhälts Du?
Windows XP SP3 (bzw. Rsaenh.dll) unterstützen PROV_RSA_AES sowie CALG_SHA_256, CALG_SHA_384, CALG_SHA_512 voll.
Ich konnte den Code also problemlos auf Windows XP SP3 mit .NET Framework 3.5 SP1 ausführen. Eine kleine Änderung habe ich dennoch vorgenommen (rsaAesCSP.PersistKeyInCsp = true), um zu verhindern dass das Schlüsselpaar u.U. gelöscht wird. Aber Du kannst die Zeile auch ganz weglassen, da PersistKeyInCsp automatisch auf true gesetzt wird, sobald es einen KeyContainerName gibt.Probleme tauchen auf, wenn man .NET-seitig Klassen wie Sha256CryptoServiceProvider (statt z.B. Sha256Managed) verwendet, die noch mit Altlasten belastet sind.
Der Konstruktor von Sha256CryptoServiceProvider wirft nämlich auf Windows XP SP3 eine PlatformNotSupportedException, obwohl SHA256 an sich vom Betriebsystem voll unterstützt wird. Die Ausnahme wird bei System.Security.Cryptography.CapiNative.AcquireCsp(String keyContainer, String providerName, ProviderType providerType, CryptAcquireContextFlags flags, Boolean throwPlatformException) geworfen. Grund dafür ist der an die Funktion übergebene providerName. Guckt man in den Code von SHA256CryptoServiceProvider, findet man dort folgende Feldinitialisierung:private CapiHashAlgorithm m_hashAlgorithm = new CapiHashAlgorithm("Microsoft Enhanced RSA and AES Cryptographic Provider", CapiNative.ProviderType.RsaAes, CapiNative.AlgorithmId.Sha256);
Schaut man jedoch in die Registrierung unter:
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Cryptography\Defaults\Provider\
oder
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Cryptography\Defaults\Provider Types\Type 024findet man einen etwas anderen Providernamen: "Microsoft Enhanced RSA and AES Cryptographic Provider (Prototype)"
Klassen wie AesCryptoServiceProvider überprüfen im Konstruktor auf Windows XP, Sha256CryptoServiceProvider jedoch nicht:
// Im Konstruktor von AesCryptoServiceProvider string providerName = "Microsoft Enhanced RSA and AES Cryptographic Provider"; if ((Environment.OSVersion.Version.Major == 5) && (Environment.OSVersion.Version.Minor == 1)) { providerName = "Microsoft Enhanced RSA and AES Cryptographic Provider (Prototype)"; }
Da nun aber der Providername in der Registry steht, könnte man HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Cryptography\Defaults\Provider\Microsoft Enhanced RSA and AES Cryptographic Provider (Prototype) exportieren, dann die reg-Datei bearbeiten und " (Prototype)" weglöschen und anschließend die reg-Datei wieder importieren. Dadurch würde ein neuer Reistrierungs-Schlüssel erzeugt werden und alles würde wie gewünscht funktionieren.Die SHA-2 Algorithmen werden nur von PROV_RSA_AES unterstützt, bei Verwendung mit PROV_RSA_FULL kommt es - wie bereits besprochen - zu einem Fehler (falscher Algorithmus).
Die Providernamen können in wincrypt.h gefunden werden.
Dokumentation dazu auch unter:Cryptographic Provider Names:
http://msdn.microsoft.com/en-us/library/aa380243(v=vs.85).aspxALG_ID
http://msdn.microsoft.com/en-us/library/aa375549(v=vs.85).aspx
Gruß
Marcel- Als Antwort markiert Aendrew Mittwoch, 3. August 2011 10:26
Alle Antworten
-
Hallo Aendrew,
Werte doch rsa_msdn.CspKeyContainerInfo.ProviderType für beide o.g. Szenarien aus. Du wirst wahrscheinlich folgendes entdecken: SHA256 funktioniert nur mit ProviderType 24 (PROV_RSA_AES), d.h. wenn Du denn CSP wie folgt erzeugst (ab Windows Server 2003):
RSACryptoServiceProvider rsa_msdn = new RSACryptoServiceProvider();
oder:
CspParameters cspParams = new CspParameters(24); RSACryptoServiceProvider rsa_msdn = new RSACryptoServiceProvider(cspParams);
Hat der Provider jedoch den ProviderTyp 1 (PROV_RSA_FULL) unterstützt er die Signierung über SHA256 einfach nicht. Du kannst nicht mit einem Algorithmus signieren, der vom Provider nicht unterstützt wird.
Erstelle Dein Zertifikat mit einem Provider vom Typ PROV_RSA_AES oder bitte den Aussteller das für Dich zu tun.
Für Provider-Typen siehe z.B.: CspParameters.ProviderType-Feld
http://msdn.microsoft.com/de-de/library/system.security.cryptography.cspparameters.providertype(v=VS.100).aspxGruß
Marcel- Als Antwort vorgeschlagen Robert BreitenhoferModerator Montag, 25. Juli 2011 15:32
- Als Antwort markiert Robert BreitenhoferModerator Mittwoch, 3. August 2011 09:53
-
Hallo Marcel,
danke für den Hinweis.
Ich hab das Mal probiert und du hattest Recht.
RSACryptoServiceProvider(); -> Microsoft Enhanced Cryptographic Provider v1.0 (Type 1)
RSACryptoServiceProvider(cspParams); -> Microsoft Enhanced RSA and AES Cryptographic Provider (Type 24)
Aber wie bekomme ich dann den privaten Key in den CryptoServiceProvider ohne in neu erzeugen zu lassen?
Es muss doch die Möglichkeit geben den CSP zu wählen und das vorhandene Zertifikat zu verwenden, oder?
-
Aber wie bekomme ich dann den privaten Key in den CryptoServiceProvider ohne in neu erzeugen zu lassen? Es muss doch die Möglichkeit geben den CSP zu wählen und das vorhandene Zertifikat zu verwenden, oder?
Hallo Aendrew,
Schau Dir mal die folgenden Links an. Vielleicht können sie Dir weiter helfen.
How can you add CSP information to an X509 certificate programmatically
*****************************************************
Invalid algorithm specified when signing with RSACryptoServiceProvider and SHA 256
Signature with SHA256withRSA-Certificate. Invalid algorithm specified
*****************************************************
The Legion of the Bouncy Castle
Grüße,
Robert
-
Hallo Aendrew,
> Aber wie bekomme ich dann den privaten Key in den CryptoServiceProvider ohne ihn neu erzeugen zu lassen? Es muss doch die Möglichkeit geben den CSP zu wählen und das vorhandene Zertifikat zu verwenden, oder?
Wenn ein CSP einen Algorithmus nicht unterstützt, ist - wie bereits erwähnt - nichts zu machen.
Du kannst aber die CSP-Parameter aus dem nicht unterstützenden CSP exportieren, einen neuen CSP erstellen (der SHA256 unterstützt) und die CSP-Parameter neu importieren.
Hier ein kleines Beispiel dazu:using System; using System.IO; using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; namespace FileSignatureTool { class Program { static void Main(string[] args) { if (args.Length != 3) { PrintUsing(); } else { string option = args[0]; string sourceFile = args[1]; string signatureFile = args[2]; byte[] inputFileBytes = File.ReadAllBytes(sourceFile); X509Certificate2 selectedCertificate = SelectCertificateFromDialog(); if (selectedCertificate != null) { RSACryptoServiceProvider rsaCSP = (RSACryptoServiceProvider)selectedCertificate.PrivateKey; RSAParameters rsaParameters = rsaCSP.ExportParameters(true); CspParameters cspParameters = new CspParameters(24); RSACryptoServiceProvider rsaAesCSP = new RSACryptoServiceProvider(cspParameters); rsaAesCSP.ImportParameters(rsaParameters); HashAlgorithm hashAlgorithm = HashAlgorithm.Create("SHA256"); if (option.ToLower() == "-sign") { // This line throws exception 'Key not valid for use in specified state' // if the private key is not marked as exportable. byte[] signedBytes = rsaAesCSP.SignData(inputFileBytes, hashAlgorithm); File.WriteAllBytes(signatureFile, signedBytes); Console.WriteLine("Die Signatur wurde erfolgreich gespeichert."); } else if (option.ToLower() == "-verify") { byte[] signedHashBytes = File.ReadAllBytes(signatureFile); bool isValid = rsaAesCSP.VerifyData(inputFileBytes, hashAlgorithm, signedHashBytes); Console.WriteLine("Signatur ist gültig: {0}", isValid ? "JA" : "NEIN"); } else { PrintUsing(); } } } Console.WriteLine("\n Drücken Sie eine beliebige Taste, um das Programm zu beenden."); Console.ReadKey(true); } private static void PrintUsing() { Console.WriteLine("\nVerwendung: FileSignatureTool.exe [-Option] [Quelldatei] [Signaturdatei]"); Console.WriteLine("\nOption:"); Console.WriteLine("\n-sign\tErstellt eine Signaturdatei"); Console.WriteLine("-verify\tVerifiziert die Signatur"); } private static X509Certificate2 SelectCertificateFromDialog() { X509Store myCurrentUserStore = new X509Store(StoreName.My, StoreLocation.CurrentUser); myCurrentUserStore.Open(OpenFlags.ReadOnly); X509Certificate2Collection certificateCollection = myCurrentUserStore.Certificates; X509Certificate2 selectedCertificate = null; X509Certificate2Collection selectedCertificateCollection = X509Certificate2UI.SelectFromCollection(certificateCollection, "Zertifikate", "Bitte wählen Sie ein Zertifikat zum Signieren aus", X509SelectionFlag.SingleSelection); if (selectedCertificateCollection.Count > 0) { X509Certificate2Enumerator en = selectedCertificateCollection.GetEnumerator(); en.MoveNext(); selectedCertificate = en.Current; } myCurrentUserStore.Close(); return selectedCertificate; } } }
Wenn Dein Ziel hingegen der ist, eine ASN.1-Datei zu erzeugen (CMS/PKCS# 7), welche die Daten signiert und wrappt (eingebettete Signatur), kannst Du einfach den Weg über CmsSigner und SignedCms nehmen:using System; using System.IO; using System.Security.Cryptography; using System.Security.Cryptography.Pkcs; using System.Security.Cryptography.X509Certificates; namespace SignFile { class Program { static void Main(string[] args) { if (args.Length != 3) { PrintUsing(); } else { string option = args[0]; string sourceFile = args[1]; string targetFile = args[2]; byte[] bytesToOperateOn = File.ReadAllBytes(sourceFile); if (option.ToLower() == "-sign") { X509Certificate2 selectedCertificate = SelectCertificateFromDialog(); if (selectedCertificate != null) { CmsSigner signer = GetSha256Signer(selectedCertificate); byte[] encodedAndSignedCmsBytes = SignAndEncodeBytes(bytesToOperateOn, signer); File.WriteAllBytes(targetFile, encodedAndSignedCmsBytes); Console.WriteLine("Die Datei wurde erfolgreich signiert."); } } else if (option.ToLower() == "-verifysave") { SignedCms cms = new SignedCms(new ContentInfo(bytesToOperateOn), false); try { cms.Decode(bytesToOperateOn); try { cms.CheckSignature(true); File.WriteAllBytes(targetFile, cms.ContentInfo.Content); Console.WriteLine("Die Signatur wurde erfolgreich überprüft."); } catch (CryptographicException cex) { Console.WriteLine("Signatur-Überprüfung fehlgeschlagen: {0}", cex.Message); } } catch (CryptographicException ex) { Console.WriteLine("Fehler beim Dekodieren der signierten Nachricht: {0}", ex.Message); } } else { PrintUsing(); } } Console.WriteLine("\n Drücken Sie eine beliebige Taste, um das Programm zu beenden."); Console.ReadKey(true); } private static void PrintUsing() { Console.WriteLine("\nVerwendung: SignFile.exe [-Option] [Quelldatei] [Zieldatei]"); Console.WriteLine("\nOption:"); Console.WriteLine("\n-sign\tSigniert und Kodiert die Datei"); Console.WriteLine("-verifysave\tVerifiziert die Signatur und speichert den\n\tdekodierten Inhalt der Quelldatei in die Zieldatei ab"); } private static byte[] SignAndEncodeBytes(byte[] bytesToSign, CmsSigner signer) { SignedCms cms = new SignedCms(new ContentInfo(bytesToSign), false); cms.ComputeSignature(signer, true); return cms.Encode(); } private static CmsSigner GetSha256Signer(X509Certificate2 selectedCertificate) { CmsSigner signer = new CmsSigner(selectedCertificate); Oid oid = new Oid("SHA256"); signer.DigestAlgorithm = oid; return signer; } private static X509Certificate2 SelectCertificateFromDialog() { X509Store myCurrentUserStore = new X509Store(StoreName.My, StoreLocation.CurrentUser); myCurrentUserStore.Open(OpenFlags.ReadOnly); X509Certificate2Collection certificateCollection = myCurrentUserStore.Certificates; X509Certificate2 selectedCertificate = null; X509Certificate2Collection selectedCertificateCollection = X509Certificate2UI.SelectFromCollection(certificateCollection, "Zertifikate", "Bitte wählen Sie ein Zertifikat zum Signieren aus", X509SelectionFlag.SingleSelection); if (selectedCertificateCollection.Count > 0) { X509Certificate2Enumerator en = selectedCertificateCollection.GetEnumerator(); en.MoveNext(); selectedCertificate = en.Current; } myCurrentUserStore.Close(); return selectedCertificate; } } }
Gruß
Marcel- Als Antwort vorgeschlagen Elmar BoyeEditor Mittwoch, 27. Juli 2011 08:35
- Als Antwort markiert Robert BreitenhoferModerator Dienstag, 2. August 2011 15:25
-
Hallo Aendrew,
Ob Du nun über das Importieren der CSP-Parameter gehst, oder die Signatur einbettest: Wenn Du keine Benutzerinteraktion beim Auswählen des Zertifikats benötigst, kannst Du natürlich mit X509Certificate2Collection.Find() das gewünschte Zertifikat auch direkt finden.
X509Certificate2Collection.Find-Methode:
http://msdn.microsoft.com/de-de/library/system.security.cryptography.x509certificates.x509certificate2collection.find(v=VS.100).aspxGruß
Marcel -
-
Hallo Marcel,
auch dir vielen Dank für deine Unterstützung.
Ob mein Problem dadurch wirklich gelöst ist muss ich erst noch prüfen.
Das richtige Zertifikat zu erhalben war nie mein Problem.
Ich bekomme aus dem Store das richtige Zertifikat:
X509Certificate2 certificate = null; if (String.IsNullOrEmpty(subjectCommonName)) { throw new PlainSignatureException(Properties.Messages.PlainSignature014); } X509Store store = new X509Store(StoreLocation.CurrentUser); store.Open(OpenFlags.ReadOnly); X509Certificate2Collection certCollection = store.Certificates; foreach (X509Certificate2 c in certCollection) { X500DistinguishedName subjectName = c.SubjectName; string subjectNameString = subjectName.Name; if (subjectNameString != null) { if (subjectNameString.Contains("CN=" + subjectCommonName)) { certificate = c; break; } } } if (certificate == null) { throw new PlainSignatureException(Properties.Messages.PlainSignature006); } store.Close(); return certificate;
Bei deinem vorherigen Beispiel wird ein Zertifikat benötigt, dass exportiert werden kann. Dies ist bei mir leider nicht der Fall.Mir wird ein Zertifikat als nicht exportierbar im Store zur Verfügung gestellt. Die Sicherheit dieses Zertifikats wird nicht auf hoch gesetzt somit benötige ich zum Anwenden kein Kennwort.
Allerdings ist mir dieses Zertifikat vorgegeben und ich kennen nur den CommonName.
Dann erhalte ich den Hash der Datei uns muss diesen mit dem Zertifikat aus dem Store signieren.
Am Schluss hab ich dann die Signatur und muss diese zurück geben.
Welchen CSP ich dafür verwende ist mir prinzipiell egal, ich würde wenn es geht den 'Microsoft Enhanced RSA and AES Cryptographic Provider' verwenden.
Allerdings funktioniert dies zur Zeit lieder nicht.
Gibt es evtl. mein Import die Möglichkeit den zu verwenden CSP zu definieren?
Gruß
Aendrew
-
Hallo Aendrew,
> Ob mein Problem dadurch wirklich gelöst ist muss ich erst noch prüfen [...]
Mein erstes Beispiel ging - wie Du richtig bemerkt hast - von einem exportierbaren Schlüssel aus. Wenn der Schlüssel nun beim Import als nicht-exportierbar gekennzeichnet wurde, muss man vom diesem Export/Import-Manöver freilich absehen.
Stattdessen kann man die CspParameters, die zur Erzeugung des neuen RSACryptoServiceProvider verwendet werden, so einstellen, dass sie auf den selben Container verweisen wie der ursprüngliche CSP, d.h.: ((RSACryptoServiceProvider) selectedCertificate.PrivateKey).CspContainerInfo.KeyContainerName.
Hier also das im Lichte der neuen Anforderungen umgearbeitete, funktionierende Beispiel, das auf Schlüsselexport verzichten kann:using System; using System.IO; using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; namespace FileSignatureTool { class Program { static void Main(string[] args) { if (args.Length != 3) { PrintUsing(); } else { string option = args[0]; string sourceFile = args[1]; string signatureFile = args[2]; byte[] inputFileBytes = File.ReadAllBytes(sourceFile); X509Certificate2 selectedCertificate = SelectCertificateFromDialog(); if (selectedCertificate != null) { RSACryptoServiceProvider rsaCSP = (RSACryptoServiceProvider)selectedCertificate.PrivateKey; // Avoiding export here due to having to use an unexportable key. // RSAParameters rsaParameters = rsaCSP.ExportParameters(true); CspParameters cspParameters = new CspParameters(); cspParameters.KeyContainerName = rsaCSP.CspKeyContainerInfo.KeyContainerName; cspParameters.KeyNumber = rsaCSP.CspKeyContainerInfo.KeyNumber == KeyNumber.Exchange ? 1 : 2; // As of .NET Framework 3.5 SP1 the default CSP has changed from PROV_RSA_FULL (1) to // PROV_RSA_AES (24) for all OS > XP, so default is 24 even without setting // CspParameters.ProviderType to 24 RSACryptoServiceProvider rsaAesCSP = new RSACryptoServiceProvider(cspParameters); rsaAesCSP.PersistKeyInCsp = false; // We have set KeyContainerName to an existing container, no need to import parameters // rsaAesCSP.ImportParameters(rsaParameters); HashAlgorithm hashAlgorithm = HashAlgorithm.Create("SHA256"); if (option.ToLower() == "-sign") { // This line throws exception 'Key not valid for use in specified state' // if the private key is not marked as exportable and we if we also have // not set the KeyContainerName to the container of the store certificate byte[] signedBytes = rsaAesCSP.SignData(inputFileBytes, hashAlgorithm); File.WriteAllBytes(signatureFile, signedBytes); Console.WriteLine("Die Signatur wurde erfolgreich gespeichert."); } else if (option.ToLower() == "-verify") { byte[] signedHashBytes = File.ReadAllBytes(signatureFile); bool isValid = rsaAesCSP.VerifyData(inputFileBytes, hashAlgorithm, signedHashBytes); Console.WriteLine("Signatur ist gültig: {0}", isValid ? "JA" : "NEIN"); } else { PrintUsing(); } } } Console.WriteLine("\n Drücken Sie eine beliebige Taste, um das Programm zu beenden."); Console.ReadKey(true); } private static void PrintUsing() { Console.WriteLine("\nVerwendung: FileSignatureTool.exe [-Option] [Quelldatei] [Signaturdatei]"); Console.WriteLine("\nOption:"); Console.WriteLine("\n-sign\tErstellt eine Signaturdatei"); Console.WriteLine("-verify\tVerifiziert die Signatur"); } private static X509Certificate2 SelectCertificateFromDialog() { X509Store myCurrentUserStore = new X509Store(StoreName.My, StoreLocation.CurrentUser); myCurrentUserStore.Open(OpenFlags.ReadOnly); X509Certificate2Collection certificateCollection = myCurrentUserStore.Certificates; X509Certificate2 selectedCertificate = null; X509Certificate2Collection selectedCertificateCollection = X509Certificate2UI.SelectFromCollection(certificateCollection, "Zertifikate", "Bitte wählen Sie ein Zertifikat zum Signieren aus", X509SelectionFlag.SingleSelection); if (selectedCertificateCollection.Count > 0) { X509Certificate2Enumerator en = selectedCertificateCollection.GetEnumerator(); en.MoveNext(); selectedCertificate = en.Current; } myCurrentUserStore.Close(); return selectedCertificate; } } }
Zur Sicherheit noch: Das Markieren eines Schlüssels als nicht exportierbar stellt keine reale security boundary dar. Es gibt genügend Tools wie jailbreak etc., die den Export trotz Markierung ermöglichen. Wie man im unten verlinkten Windows PKI Blogartikel lesen kann wird beim Markieren einfach im Key-Storage ein Flag gesetzt (NCRYPT_ALLOW_EXPORT_FLAG), das Zertifikat selber weiß nichts davon.Windows PKI blog - Marking private keys as non-exportable with certutil -importpfx:
http://blogs.technet.com/b/pki/archive/2007/07/29/marking-private-keys-as-non-exportable-with-certutil-importpfx.aspxKey Storage Property Identifiers:
http://msdn.microsoft.com/en-us/library/aa376242.aspxGruß
Marcel- Bearbeitet Marcel RomaModerator Mittwoch, 27. Juli 2011 21:24 Code-Beispiel hinzugefügt
- Als Antwort vorgeschlagen Marcel RomaModerator Montag, 1. August 2011 14:37
- Als Antwort markiert Aendrew Dienstag, 2. August 2011 15:08
-
Hallo Marcel,
leider hab ich es gestern nicht geschafft, aber heute wurde ein Schuh draus.
Echt super Leistung, vielen Dank funktioniert unter Windows 7 bestens.
Leider klappt es unter Windows XP nicht, war zwar der Meinung, dass mit SP3 auch SHA256 unterstützt wird aber scheinbar gibts hier noch Probleme.
Implements and supports the SHA2 hashing algorithms (SHA256, SHA384, and SHA512) in X.509 certificate validation. This has been added to the crypto module rsaenh.dll.
Nochmals vielen Dank.Gruß
Aendrew
-
Hallo Aendrew,
> funktioniert unter Windows 7 bestens. Leider klappt es unter Windows XP nicht [...] scheinbar gibts hier noch Probleme.
Was meinst Du genau mit "klappt es unter Windows XP nicht"? - Welche Fehlermeldung erhälts Du?
Windows XP SP3 (bzw. Rsaenh.dll) unterstützen PROV_RSA_AES sowie CALG_SHA_256, CALG_SHA_384, CALG_SHA_512 voll.
Ich konnte den Code also problemlos auf Windows XP SP3 mit .NET Framework 3.5 SP1 ausführen. Eine kleine Änderung habe ich dennoch vorgenommen (rsaAesCSP.PersistKeyInCsp = true), um zu verhindern dass das Schlüsselpaar u.U. gelöscht wird. Aber Du kannst die Zeile auch ganz weglassen, da PersistKeyInCsp automatisch auf true gesetzt wird, sobald es einen KeyContainerName gibt.Probleme tauchen auf, wenn man .NET-seitig Klassen wie Sha256CryptoServiceProvider (statt z.B. Sha256Managed) verwendet, die noch mit Altlasten belastet sind.
Der Konstruktor von Sha256CryptoServiceProvider wirft nämlich auf Windows XP SP3 eine PlatformNotSupportedException, obwohl SHA256 an sich vom Betriebsystem voll unterstützt wird. Die Ausnahme wird bei System.Security.Cryptography.CapiNative.AcquireCsp(String keyContainer, String providerName, ProviderType providerType, CryptAcquireContextFlags flags, Boolean throwPlatformException) geworfen. Grund dafür ist der an die Funktion übergebene providerName. Guckt man in den Code von SHA256CryptoServiceProvider, findet man dort folgende Feldinitialisierung:private CapiHashAlgorithm m_hashAlgorithm = new CapiHashAlgorithm("Microsoft Enhanced RSA and AES Cryptographic Provider", CapiNative.ProviderType.RsaAes, CapiNative.AlgorithmId.Sha256);
Schaut man jedoch in die Registrierung unter:
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Cryptography\Defaults\Provider\
oder
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Cryptography\Defaults\Provider Types\Type 024findet man einen etwas anderen Providernamen: "Microsoft Enhanced RSA and AES Cryptographic Provider (Prototype)"
Klassen wie AesCryptoServiceProvider überprüfen im Konstruktor auf Windows XP, Sha256CryptoServiceProvider jedoch nicht:
// Im Konstruktor von AesCryptoServiceProvider string providerName = "Microsoft Enhanced RSA and AES Cryptographic Provider"; if ((Environment.OSVersion.Version.Major == 5) && (Environment.OSVersion.Version.Minor == 1)) { providerName = "Microsoft Enhanced RSA and AES Cryptographic Provider (Prototype)"; }
Da nun aber der Providername in der Registry steht, könnte man HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Cryptography\Defaults\Provider\Microsoft Enhanced RSA and AES Cryptographic Provider (Prototype) exportieren, dann die reg-Datei bearbeiten und " (Prototype)" weglöschen und anschließend die reg-Datei wieder importieren. Dadurch würde ein neuer Reistrierungs-Schlüssel erzeugt werden und alles würde wie gewünscht funktionieren.Die SHA-2 Algorithmen werden nur von PROV_RSA_AES unterstützt, bei Verwendung mit PROV_RSA_FULL kommt es - wie bereits besprochen - zu einem Fehler (falscher Algorithmus).
Die Providernamen können in wincrypt.h gefunden werden.
Dokumentation dazu auch unter:Cryptographic Provider Names:
http://msdn.microsoft.com/en-us/library/aa380243(v=vs.85).aspxALG_ID
http://msdn.microsoft.com/en-us/library/aa375549(v=vs.85).aspx
Gruß
Marcel- Als Antwort markiert Aendrew Mittwoch, 3. August 2011 10:26
-
Hallo Marcel,
du begeisterst mich jedes mal auf neue ;)
Hab das Problem heute Morgen selbst gefunden und wollte das heute Abend auch noch posten...
Wie du richtig vermutet hast: Es lag am CSP.
Ich hab beim Erzeugen des CSPParameters folgenden Code verwendet:
CspParameters cspParameters = new CspParameters(24, "Microsoft Enhanced RSA and AES Cryptographic Provider");
Wie du richtig beschrieben hast nennt sich das CSP unter XP SP3 anders.Hab jetzt wieder deinen Code fast unverändert übernommen und nun funktioniert es bei meinen Tests auch in allen Lebenslagen.
Vielen Dank für die ganzen Infos, Links, Tipps und Tricks
Gruß
Aendrew