wake-up-neo.com

Wie kann ich eine Datei mit RSA und SHA256 mit .NET signieren?

Meine Anwendung nimmt eine Reihe von Dateien und signiert sie. (Ich versuche nicht, eine Assembly zu unterzeichnen.) Es gibt eine P12-Datei, aus der ich den privaten Schlüssel bekomme.

Dies ist der Code, den ich verwenden wollte, aber ich bekomme einen System.Security.Cryptography.CryptographicException "Invalid algorithm specified.".

X509Certificate pXCert = new X509Certificate2(@"keyStore.p12", "password");
RSACryptoServiceProvider csp = (RSACryptoServiceProvider)pXCert.PrivateKey;
string id = CryptoConfig.MapNameToOID("SHA256");
return csp.SignData(File.ReadAllBytes(filePath), id);

Nach dieser Antwort ist dies nicht möglich (die RSACryptoServiceProvider unterstützt kein SHA-256), aber ich hatte gehofft, dass es möglich sein könnte, eine andere Bibliothek zu verwenden, wie zB Bouncy Castle.

Ich bin neu in diesem Zeug und finde Bouncy Castle sehr verwirrend. Ich portiere eine Java-App nach C # und muss die gleiche Art der Verschlüsselung verwenden, um die Dateien zu signieren. Ich stecke also mit RSA + SHA256 fest.

Wie kann ich dies mit Bouncy Castle, OpenSSL.NET, Security.Cryptography oder einer anderen Drittanbieter-Bibliothek tun, von der ich noch nichts gehört habe? Ich gehe davon aus, wenn es in Java gemacht werden kann, dann in C #.

AKTUALISIEREN:

dies ist, was ich von dem Link in Poupou's Antwort erhalten habe

        X509Certificate2 cert = new X509Certificate2(KeyStoreFile, password");
        RSACryptoServiceProvider rsacsp = (RSACryptoServiceProvider)cert.PrivateKey;
        CspParameters cspParam = new CspParameters();
        cspParam.KeyContainerName = rsacsp.CspKeyContainerInfo.KeyContainerName;
        cspParam.KeyNumber = rsacsp.CspKeyContainerInfo.KeyNumber == KeyNumber.Exchange ? 1 : 2;
        RSACryptoServiceProvider aescsp = new RSACryptoServiceProvider(cspParam);
        aescsp.PersistKeyInCsp = false;
        byte[] signed = aescsp.SignData(File.ReadAllBytes(file), "SHA256");
        bool isValid = aescsp.VerifyData(File.ReadAllBytes(file), "SHA256", signed);

Das Problem ist, dass ich nicht die gleichen Ergebnisse bekomme wie mit dem ursprünglichen Werkzeug. Soweit ich das Lesen des Codes feststellen kann, verwendet der CryptoServiceProvider, der die eigentliche Signatur ausführt, nicht den PrivateKey aus der Schlüsselspeicherdatei. Ist das korrekt?

41
scott

RSA + SHA256 kann und wird funktionieren ...

Ihr späteres Beispiel kann nicht immer funktionieren, es sollte die OID des Hash-Algorithmus und nicht dessen Namen verwenden. Wie in Ihrem ersten Beispiel wird dies durch einen Aufruf an [CryptoConfig.MapNameToOID] (AlgorithmusName) 1 erhalten, wobei AlgorithmusName Ihren Angaben entspricht (d. H. "SHA256").

Zuerst benötigen Sie das Zertifikat mit dem privaten Schlüssel. Normalerweise lese ich meine aus dem LocalMachine- oder CurrentUser-Speicher, indem ich eine öffentliche Schlüsseldatei (.cer) zum Identifizieren des privaten Schlüssels verwende und dann die Zertifikate auflisten und den Hashwert abgleichen ...

X509Certificate2 publicCert = new X509Certificate2(@"C:\mycertificate.cer");

//Fetch private key from the local machine store
X509Certificate2 privateCert = null;
X509Store store = new X509Store(StoreLocation.LocalMachine);
store.Open(OpenFlags.ReadOnly);
foreach( X509Certificate2 cert in store.Certificates)
{
    if (cert.GetCertHashString() == publicCert.GetCertHashString())
        privateCert = cert;
}

Wenn Sie jedoch ein Zertifikat mit einem privaten Schlüssel erhalten, müssen Sie es rekonstruieren. Dies ist möglicherweise auf die Art und Weise erforderlich, wie das Zertifikat seinen privaten Schlüssel erstellt. Ich weiß jedoch nicht genau, warum. Auf jeden Fall exportieren wir den Schlüssel zuerst und importieren ihn anschließend in einem beliebigen Zwischenformat erneut. Das einfachste ist xml:

//Round-trip the key to XML and back, there might be a better way but this works
RSACryptoServiceProvider key = new RSACryptoServiceProvider();
key.FromXmlString(privateCert.PrivateKey.ToXmlString(true));

Sobald dies erledigt ist, können wir ein Datum wie folgt signieren:

//Create some data to sign
byte[] data = new byte[1024];

//Sign the data
byte[] sig = key.SignData(data, CryptoConfig.MapNameToOID("SHA256"));

Schließlich kann die Überprüfung direkt mit dem öffentlichen Schlüssel des Zertifikats durchgeführt werden, ohne dass die Wiederherstellung wie bei dem privaten Schlüssel erforderlich ist:

key = (RSACryptoServiceProvider)publicCert.PublicKey.Key;
if (!key.VerifyData(data, CryptoConfig.MapNameToOID("SHA256"), sig))
    throw new CryptographicException();
50
csharptest.net

Die Verwendung von privateKey.toXMLString (true) oder privateKey.exportParameters (true) kann in einer sicheren Umgebung nicht verwendet werden, da der private Schlüssel exportiert werden muss. Dies ist NICHT empfehlenswert.

Eine bessere Lösung ist das explizite Laden des "Enhanced" Crypto Providers als solchen:

// Find my openssl-generated cert from the registry
var store = new X509Store(StoreLocation.LocalMachine);
store.Open(OpenFlags.ReadOnly);
var certificates = store.Certificates.Find(X509FindType.FindBySubjectName, "myapp.com", true);
var certificate = certificates[0];
store.Close();
// Note that this will return a Basic crypto provider, with only SHA-1 support
var privKey = (RSACryptoServiceProvider)certificate.PrivateKey;
// Force use of the Enhanced RSA and AES Cryptographic Provider with openssl-generated SHA256 keys
var enhCsp = new RSACryptoServiceProvider().CspKeyContainerInfo;
var cspparams = new CspParameters(enhCsp.ProviderType, enhCsp.ProviderName, privKey.CspKeyContainerInfo.KeyContainerName);
privKey = new RSACryptoServiceProvider(cspparams);
18
BKibler

Ich entschied mich für das Ändern der Schlüsseldatei, um den entsprechenden Crypto Service Provider anzugeben, um das Problem in .NET zu vermeiden.

Wenn ich also aus einem privaten PEM-Schlüssel und einem öffentlichen CRT-Zertifikat eine PFX-Datei erstelle, mache ich es folgendermaßen:

openssl pkcs12 -export -aes256 -CSP "Microsoft Enhanced RSA and AES Cryptographic Provider" -inkey priv.pem -in pub.crt -out priv.pfx

Der Schlüsselteil war -CSP "Microsoft Enhanced RSA und AES Cryptographic Provider".

(-inkey gibt die private Schlüsseldatei und -in das zu integrierende öffentliche Zertifikat an.)

Möglicherweise müssen Sie dies für die verfügbaren Dateiformate anpassen. Die Befehlszeilenbeispiele auf dieser Seite können dabei helfen: https://www.sslshopper.com/ssl-converter.html

Ich habe diese Lösung hier gefunden: http://hintdesk.com/c-how-to-fix-invalid-algorithm-specified-when-signing-with-sha256/

8
Timo

So bin ich mit diesem Problem umgegangen:

 X509Certificate2 privateCert = new X509Certificate2("certificate.pfx", password, X509KeyStorageFlags.Exportable);

 // This instance can not sign and verify with SHA256:
 RSACryptoServiceProvider privateKey = (RSACryptoServiceProvider)privateCert.PrivateKey;

 // This one can:
 RSACryptoServiceProvider privateKey1 = new RSACryptoServiceProvider();
 privateKey1.ImportParameters(privateKey.ExportParameters(true));

 byte[] data = Encoding.UTF8.GetBytes("Data to be signed"); 

 byte[] signature = privateKey1.SignData(data, "SHA256");

 bool isValid = privateKey1.VerifyData(data, "SHA256", signature);
7
user3658415

Wenn Sie ein Zertifikat verwenden, um Ihren RSACryptoServiceProvider zu erhalten, ist es wirklich wichtig, welcher CryptoAPI-Anbieter zugrunde liegt. Wenn Sie ein Zertifikat mit "makecert" erstellen, wird standardmäßig "RSA-FULL" verwendet, das nur SHA1-Hashes für die Signatur unterstützt. Sie benötigen den neuen "RSA-AES", der SHA2 unterstützt.

So können Sie Ihr Zertifikat mit einer zusätzlichen Option erstellen: -sp "Microsoft Enhanced RSA- und AES-Kryptografieanbieter" (oder ein entsprechendes -sy 24), und dann würde Ihr Code ohne die Schlüsselverarbeitung funktionieren.

5
Kastorskij

So signiere ich eine Zeichenfolge, ohne das Zertifikat ändern zu müssen (an einen Microsoft Enhanced RSA- und AES Cryptographic-Anbieter).

        byte[] certificate = File.ReadAllBytes(@"C:\Users\AwesomeUser\Desktop\Test\ServerCertificate.pfx");
        X509Certificate2 cert2 = new X509Certificate2(certificate, string.Empty, X509KeyStorageFlags.Exportable);
        string stringToBeSigned = "This is a string to be signed";
        SHA256Managed shHash = new SHA256Managed();
        byte[] computedHash = shHash.ComputeHash(Encoding.Default.GetBytes(stringToBeSigned));


        var certifiedRSACryptoServiceProvider = cert2.PrivateKey as RSACryptoServiceProvider;
        RSACryptoServiceProvider defaultRSACryptoServiceProvider = new RSACryptoServiceProvider();
        defaultRSACryptoServiceProvider.ImportParameters(certifiedRSACryptoServiceProvider.ExportParameters(true));
        byte[] signedHashValue = defaultRSACryptoServiceProvider.SignData(computedHash, "SHA256");
        string signature = Convert.ToBase64String(signedHashValue);
        Console.WriteLine("Signature : {0}", signature);

        RSACryptoServiceProvider publicCertifiedRSACryptoServiceProvider = cert2.PublicKey.Key as RSACryptoServiceProvider;
        bool verify = publicCertifiedRSACryptoServiceProvider.VerifyData(computedHash, "SHA256", signedHashValue);
        Console.WriteLine("Verification result : {0}", verify);
4
AdmiralThrawn

Entsprechend diesem blog it sollte arbeiten Sie mit FX 3.5 (siehe Hinweis unten). Es sei jedoch daran erinnert, dass der größte Teil der .NET-Kryptographie auf CryptoAPI basiert (auch wenn CNG in aktuellen FX-Releases immer mehr verfügbar ist). 

Der Schlüssel -Punkt ist, dass die Unterstützung des CryptoAPI -Algorithmus von dem verwendeten Crypto Service Provider (CSP) abhängt und das zwischen den Windows-Versionen ein wenig variiert (dh was unter Windows 7 funktioniert funktioniert möglicherweise nicht unter Windows 2000).

Lesen Sie die Kommentare (aus dem Blogeintrag), um eine Umgehungslösung für möglich anzuzeigen, in der Sie den AES-CSP (anstelle des Standard-CSP) angeben, wenn Sie die Instanz RSACCryptoServiceProvider erstellen. Das scheint bei manchen Leuten zu funktionieren, YMMV. 

Hinweis: Dies ist für viele Menschen verwirrend, da alle veröffentlichten .NET-Frameworks eine managed -Implementierung von SHA256 enthalten, die von CryptoAPI nicht verwendet werden kann. FWIW Mono leidet nicht unter solchen Problemen ;-)

3
poupou

Use kann dies für neuere Frameworks verwenden.

    public byte[] GetSignature(byte[] inputData)
    {
        using (var rsa = this.signingCertificate.GetRSAPrivateKey())
        {
            return rsa.SignData(inputData, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
        }
    }

    public bool ValidateSignature(byte[] inputData, byte[] signature)
    {
        using (var rsa = this.signingCertificate.GetRSAPublicKey())
        {
            return rsa.VerifyData(inputData, signature, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
        }
    }

Die signingCertificate oben ist ein X509Certificate2 mit einem privaten Schlüssel. Bei dieser Methode müssen Sie keine vorhandenen Schlüssel importieren und arbeiten in einer sicheren Umgebung.

1
Vinay Chandra

Ich habe ähnliche Probleme in .NET festgestellt, bei denen der falsche private Schlüssel verwendet wurde (oder waren es platte Fehler? Ich kann mich nicht erinnern), wenn sich das Zertifikat, mit dem ich arbeite, nicht im Benutzer-/Computerzertifikatspeicher befindet. Durch die Installation im Speicher wurde das Problem für mein Szenario behoben, und die Dinge funktionierten wie erwartet - vielleicht können Sie es versuchen.

0
Sander