Получить PEM из файлов p7 + p8, используя C # - PullRequest
0 голосов
/ 22 сентября 2018

Intro

Я работаю над преобразованием библиотеки Java в .Net.

Библиотека представляет собой реализацию расшифровки полиморфного псевдонима и будет использоваться в Нидерландахрасшифровать "BSNk" в области европейских служб электронной идентификации eIDAS.

Я уже преобразовал большую часть библиотеки и работал с автором версии Java для проверки результатов.Следующий шаг - сделать библиотеку .Net действительно полезной для голландских компаний, и вот где я застрял последние 2 недели.

Алгоритмы используют эллиптическую кривую в файле PEM в качестве одной из частей.для расчета.Но клиенты (пользователи библиотеки) получат это в виде файла p7 и p8, который можно преобразовать / извлечь / декодировать (?) В данные PEM.

Вопрос

Как я могу получить из файлов p7 + p8 строку PEM в C #?

Желательно использовать только System.Security.Cryptography.Pkcs, но в настоящее время я использую BouncyCastle в другихчасти (потому что версия Java сделала).Не перечислено ниже, но я также пытался сделать это, используя SignedCms и EnvelopedCms, но ничего не получил, кроме (для меня) непонятных ошибок.У меня нет большого опыта в криптографии, но я многому научился за последние несколько недель.

Если я правильно понимаю, я бы объяснил это, поскольку файл p7 является конвертом PEMсообщение, а конверт подписан / зашифрован с использованием закрытого ключа в файле p8?

код

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

обновление 8-10-2018 Следуя совету автора библиотеки Java, я попытался пропустить проблему автоматического преобразования в PEM и просто использовать openssl для его расшифровки.К сожалению, команда openssl для расшифровки файлов также не работает!Как в Windows, так и в Linux.Странно то, что это делается с использованием тех же файлов, которые прекрасно работают при использовании в библиотеке Java.Р8 коррумпирован?Это как-то совместимо только при использовании в Java JceKeyTransEnvelopedRecipient ???

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

(я также пытался использовать PEM вместо DER, но безрезультатно. Файлы находятся в репозитории GitHub)

enter image description here

Обновление 9-10-2018 Спасибо Карлу, который выяснил причину, по-видимому, поврежденного файла p8.Вместо того, чтобы напрямую расшифровывать его с помощью openssl cms, нам нужно было сначала преобразовать двоичный DER p8 в PEM, закодированный в base64.

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

Мы могли бы также сделать это в c #, считав байты из файла p8, преобразовав ихк Base64 и добавив вокруг него верхний / нижний колонтитул BEGIN / END PRIVATE KEY.

Ресурсы

Вы можете увидеть, что этот код используется и не проходит как модульный тест в моемпроект.Проект также включает в себя соответствующие файлы p7, p8 и PEM для тестирования.

Версию Java можно найти здесь: https://github.com/BramvanPelt/PPDecryption

Моя версия незавершенной работы находится здесь: https://github.com/MartijnKooij/PolymorphicPseudonymisation

Ответы [ 2 ]

0 голосов
/ 19 октября 2018

Вы сможете достичь своих целей с помощью .NET 4.7.2:

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 голосов
/ 14 октября 2018

Наконец я смог успешно расшифровать сообщение;похоже, что API BouncyCastle игнорируют директиву OAEP SHA-256 и придерживаются SHA-1 OAEP, что приводит к исключению заполнения.Кроме того, как я обнаружил, API-интерфейсы Microsoft используют X509Certificate2, которые поддерживают только RsaCryptoServiceProvider с поддержкой SHA-1 OAEP.Требуется более новый RsaCng для поддержки SHA-256 OAEP.Я думаю, что нам нужно поднять тикет с помощью corefx (https://github.com/dotnet/corefx), а также bc-csharp (https://github.com/bcgit/bc-csharp).

). Следующий код на c # расшифрует сообщение; с помощью Microsoft API:

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

Реализация StringToByteArray выглядит следующим образом:

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