web-dev-qa-db-de.com

Holen Sie sich PEM aus p7 + p8-Dateien mit C #

Intro

Ich arbeite an der Konvertierung einer Java-Bibliothek in .Net.

Die Bibliothek ist eine Implementierung der polymorphen Pseudonym-Entschlüsselung und wird in den Niederlanden zur Entschlüsselung von "BSNk" s im Bereich der elektronischen eIDAS-Identifizierungsdienste verwendet.

Ich habe den größten Teil der Bibliothek bereits konvertiert und mit dem Autor der Java-Version zusammengearbeitet, um die Ergebnisse zu überprüfen. Der nächste Schritt besteht darin, die .Net-Bibliothek für niederländische Unternehmen nutzbar zu machen, und da habe ich mich für die letzte 2 Wochen.

Die Algorithmen verwenden eine elliptische Kurve in einer PEM-Datei als einen der Teile für die Berechnung. Die Clients (die Benutzer der Bibliothek) erhalten diese jedoch in Form einer P7- und einer P8-Datei, die Sie in die PEM-Daten konvertieren/extrahieren/decodieren können.

Frage

Wie komme ich von den p7 + p8-Dateien zu einer PEM-Zeichenfolge in C #?

Vorzugsweise nur mit System.Security.Cryptography.Pkcs, aber ich benutze BouncyCastle derzeit in anderen Teilen (weil die Java-Version dies getan hat). Nicht aufgeführt, aber ich habe auch versucht, dies mit SignedCms und EnvelopedCms zu tun, aber nichts aber (für mich) unverständliche Fehler daraus. Ich habe nicht viel Erfahrung in der Kryptographie, habe aber in den letzten Wochen einiges gelernt.

Wenn ich es richtig verstehe, würde ich dies als P7-Datei erklären, die der Umschlag der PEM-Nachricht ist, und der Umschlag wird mit dem privaten Schlüssel in der P8-Datei signiert/verschlüsselt.

Code

public static string ConvertToPem(string p7File, string p8File)
{
    var p7Data = File.ReadAllBytes(p7File);
    var p8Data = File.ReadAllBytes(p8File);

    // Java version gets the private key like this:
    // KeyFactory.getInstance("RSA").generatePrivate(new PKCS8EncodedKeySpec(bytesArray));
    var privateKey = PrivateKeyFactory.CreateKey(p8Data);

    var parser = new CmsEnvelopedDataParser(p7Data);
    var recipients = parser.GetRecipientInfos().GetRecipients().OfType<RecipientInformation>();
    var recipientInformation = recipients.First();

    //Java version gets the message like this:
    //final byte[] message = keyInfo.getContent(new JceKeyTransEnvelopedRecipient(key).setProvider("BC"));

    var keyInfo = (KeyTransRecipientInformation)recipientInformation;
    var message = keyInfo.GetContent(privateKey);

    return Encoding.ASCII.GetString(message);
}

Update 8-10-2018 Nach einem Tipp des Autors der Java-Bibliothek habe ich versucht, das Problem der automatischen Konvertierung in PEM zu überspringen und es einfach mit openssl zu entschlüsseln. Leider schlägt auch der Befehl openssl zum Entschlüsseln der Dateien fehl! Sowohl unter Windows als auch unter Linux. Das Merkwürdige daran ist, dass dies mit denselben Dateien gemacht wird, die in der Java-Bibliothek einwandfrei funktionieren. Ist der p8 korrupt? Ist es irgendwie nur kompatibel, wenn es in Java JceKeyTransEnvelopedRecipient verwendet wird ???

openssl cms -decrypt -inform DER -in dv_keys_ID_D_oin.p7 -inkey privatep8.key -out id.pem

(Ich habe auch versucht, PEM anstelle von DER zu verwenden, jedoch ohne Erfolg. Die Dateien befinden sich im GitHub-Repo.)

 enter image description here

Update 9-10-2018 Danke an Carl, der die Ursache der scheinbar korrupten p8-Datei herausgefunden hat. Anstatt es direkt mit openssl cms zu entschlüsseln, mussten wir zuerst das binäre DER p8 in ein base64-codiertes PEM konvertieren.

openssl pkcs8 -inform der -outform pem -in private.p8 -out private-p8.pem -topk8 -nocrypt

Wir könnten dies auch in c # tun, indem wir die Bytes aus der p8-Datei lesen, sie in Base64 konvertieren und die Kopf-/Fußzeile BEGIN/END PRIVATE KEY hinzufügen.

Ressourcen

Dieser Code wird in meinem Projekt als Unit-Test verwendet und schlägt fehl. Das Projekt enthält auch übereinstimmende p7-, p8- und PEM-Dateien zum Testen.

Die Java-Version finden Sie hier: https://github.com/BramvanPelt/PPDecryption

Meine Work-in-Progress-Version finden Sie hier: https://github.com/MartijnKooij/PolymorphicPseudonymisation

9
Martijn Kooij

Schließlich konnte ich die Nachricht erfolgreich entschlüsseln. Es sieht so aus, als würden die BouncyCastle-APIs die SHA-256-OAEP-Direktive ignorieren und sich an SHA-1-OAEP halten, was zu einer Padding-Ausnahme führt. Darüber hinaus nutzen die Microsoft-APIs X509Certificate2, die nur RsaCryptoServiceProvider mit SHA-1-OAEP-Unterstützung unterstützen, soweit ich das festgestellt habe. Man benötigt die neuere RsaCng für die SHA-256-OAEP-Unterstützung. Ich denke, wir müssen bei corefx ( https://github.com/dotnet/corefx ) sowie bc-csharp ( https://github.com/bcgit/bc-csharp ).

Der folgende C # -Code entschlüsselt die Nachricht. Verwendung von Microsoft-APIs:

// Read the RSA private key:
var p8Data = File.ReadAllBytes(@"resources\private.p8");    
CngKey key = CngKey.Import(p8Data, CngKeyBlobFormat.Pkcs8PrivateBlob);
var rsaprovider = new RSACng(key);

// Process the enveloped CMS structure:
var p7Data = File.ReadAllBytes(@"resources\p7\ID-4.p7");
var envelopedCms = new System.Security.Cryptography.Pkcs.EnvelopedCms();
envelopedCms.Decode(p7Data);
var recipients = envelopedCms.RecipientInfos;
var firstRecipient = recipients[0];

// Decrypt the AES-256 CBC session key; take note of enforcing OAEP SHA-256:
var result = rsaprovider.Decrypt(firstRecipient.EncryptedKey, RSAEncryptionPadding.OaepSHA256);

// Build out the AES-256 CBC decryption:
RijndaelManaged alg = new RijndaelManaged();
alg.KeySize = 256;
alg.BlockSize = 128;
alg.Key = result;

// I used an ASN.1 parser (https://lapo.it/asn1js/) to grab the AES IV from the PKCS#7 file.
// I could not find an API call to get this from the enveloped CMS object:
string hexstring = "919D287AAB62B672D6912E72D5DA29CD"; 
var iv = StringToByteArray(hexstring);
alg.IV = iv;
alg.Mode = CipherMode.CBC;
alg.Padding = PaddingMode.PKCS7;

// Strangely both BouncyCastle as well as the Microsoft API report 406 bytes;
// whereas https://lapo.it/asn1js/ reports only 400 bytes. 
// The 406 bytes version results in an System.Security.Cryptography.CryptographicException 
// with the message "Length of the data to decrypt is invalid.", so we strip it to 400 bytes:
byte[] content = new byte[400];
Array.Copy(envelopedCms.ContentInfo.Content, content, 400);
string decrypted = null;
ICryptoTransform decryptor = alg.CreateDecryptor(alg.Key, alg.IV);
using (var memoryStream = new MemoryStream(content)) {
    using (var cryptoStream = new CryptoStream(memoryStream, alg.CreateDecryptor(alg.Key, alg.IV), CryptoStreamMode.Read)) {
        decrypted = new StreamReader(cryptoStream).ReadToEnd();
    }
}

Die Implementierung von StringToByteArray sieht wie folgt aus:

public static byte[] StringToByteArray(String hex) {
    NumberChars = hex.Length;
    byte[] bytes = new byte[NumberChars / 2];
    for (int i = 0; i < NumberChars; i += 2)
        bytes[i / 2] = Convert.ToByte(hex.Substring(i, 2), 16);
    return bytes;
}
1
Carl in 't Veld

Sie sollten in der Lage sein, Ihre Ziele mit .NET 4.7.2 zu erreichen:

using (CngKey key = CngKey.Import(p8Data, CngKeyBlobFormat.Pkcs8PrivateBlob))
{
    // The export policy needs to be redefined because CopyWithPrivateKey
    // needs to export/re-import ephemeral keys
    key.SetProperty(
        new CngProperty(
            "Export Policy",
            BitConverter.GetBytes((int)CngExportPolicies.AllowPlaintextExport),
            CngPropertyOptions.Persist));

    using (RSA rsa = new RSACng(key))
    using (X509Certificate2 cert = new X509Certificate2(certData))
    using (X509Certificate2 certWithKey = cert.CopyWithPrivateKey(rsa))
    {
        EnvelopedCms cms = new EnvelopedCms();
        cms.Decode(p7Data);
        cms.Decrypt(new X509Certificate2Collection(certWithKey));

        // I get here reliably with your reference documents
    }
}
0
bartonjs