【问题标题】:Get PEM from p7+p8 files using C#使用 C# 从 p7+p8 文件中获取 PEM
【发布时间】:2018-09-22 19:11:28
【问题描述】:

简介

我正在将 Java 库转换为 .Net。

该库是多态假名解密的实现,将在荷兰用于解密欧洲 eIDAS 电子识别服务领域的“BSNk”。

我已经转换了大部分库,并与 Java 版本的作者一起验证了结果。 下一步是让 .Net 库真正可用于荷兰公司,而这正是我在过去 2 周中遇到的问题。

算法使用 PEM 文件中的椭圆曲线作为计算部分之一。但是客户端(库的用户)会以 p7 和 p8 文件的形式收到此文件,您可以将其转换/提取/解码(?)为 PEM 数据。

问题

如何从 te p7+p8 文件获取 C# 中的 PEM 字符串?

最好只使用 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);
}

2018 年 8 月 10 日更新 根据 Java 库作者的提示,我尝试跳过自动转换为 PEM 并仅使用 openssl 对其进行解密的问题。不幸的是,用于解密文件的 openssl 命令也失败了!无论是在 Windows 上还是在 Linux 上。奇怪的是,这是使用在 Java 库中使用时可以正常工作的相同文件完成的。 p8坏了吗?它是否仅在 Java JceKeyTransEnvelopedRecipient 中使用时才兼容???

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

(我也尝试使用 PEM 代替 DER,但无济于事。文件位于 GitHub 存储库中)

2018 年 9 月 10 日更新 感谢 Carl 找出了看似损坏的 p8 文件的原因。我们必须先将二进制 DER p8 转换为 base64 编码的 PEM,而不是直接使用 openssl cms 对其进行解密。

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

【问题讨论】:

  • 您应该可以使用 BouncyCastle 的 PemParser 读取 p8 密钥文件
  • 我正在寻找它的反面。我有 p7 数据和 p8 密钥,我需要从中创建一个 PEM。如果我没记错的话,PemParser 用于读取 PEM 数据。
  • 根据文档,PemParser:用于解析包含 X509 证书、PKCS8 编码密钥和 PKCS7 对象的 OpenSSL PEM 编码流的类。
  • PemParser 似乎已在 C# 中重命名为 PemReader。该评论确实在 PemReader 类之上,但它所能做的就是在 PEM 文件中读取......至少,这就是我能在其中找到的全部内容。还有一个 PEMWriter 但我找不到与 p7 和 p8 数据的关系...
  • 你得到了这个糟糕的填充异常,对吧?会不会是 c# 实现包含错误?我正在考虑的一种策略是获取 Bouncy Castle c# 源代码及其单元测试,并使用与 pkcs#7 文件使用的加密算法相同的加密算法定义单元测试。其他策略是试用 Microsoft 库,但随后我们需要设置 x509 证书集合。 x509 证书是否可以从您拥有的资源中轻松制作?

标签: c# cryptography bouncycastle pem pkcs#7


【解决方案1】:

我终于能够成功解密消息了;看起来 BouncyCastle API 忽略了 SHA-256 OAEP 指令并坚持使用 SHA-1 OAEP,这会导致填充异常。此外,据我所知,Microsoft API 利用 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;
}

【讨论】:

  • 看起来不错,卡尔,干得好。我将测试您的解决方案并尽快将答案标记为有效。您是否也想为此问题的 PR 工作,或者堆栈溢出的名声足以满足您的需求吗?
  • 2 cmets: 1. 您可能应该提到(或包括来源)StringToByteArray 用于将 HEX 字符串转换为字节数组。 2. 而不是 RijndaelManaged 上的 PaddingMode.Zeros,我不得不使用 PaddingMode.PKCS7 来使结果与提供的 PEM 相同。这只是 PEM 页脚后的空白。
  • 嗨@MartijnKooij,很高兴使用此解决方案提交拉取请求。虽然我们首先需要添加一个 ASN.1 解析器来从 CMS 文件中获取 AES IV。同时,我将根据您的反馈修改建议的解决方案。
【解决方案2】:

您应该能够使用 .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
    }
}

【讨论】:

  • 现在试试这个。在第三个 using 语句中,您使用了一个未定义的变量 certData,这是一个错字还是缺少什​​么?仍在 .Net Core 中搜索 CopyWithPrivateKey,我认为它应该在那里,但我还找不到它......
  • CopyWithPrivateKey 不在网络标准 2.0 中...它在完整的 4.7 中的 Core 2.0 中,但不在标准中...不确定这部分是否值得创建 2 个实现。有什么想法吗?
  • certData 是“我假设您在某处有收件人证书的副本”。至于 CopyWithPrivateKey,如果这是您必须进行双重编译的唯一原因,您可以制作自己的扩展方法以从 RSACertificateExtensions 类型中反射调用它。 (有点脆弱)
  • 我只有p7和p8,不多不少。它们包含我在算法的其余部分需要的 EC 私钥 PEM。 RSA 扩展依赖于包含平台特定代码的 CertificatePAL,因此很遗憾,提取和维护会很痛苦。
  • 反射与复制代码不同......正如您所见,复制它真的行不通。您的参考链接在 p8 旁边有证书,所以我认为这是公平的游戏。如果你真的没有证书,你可以用 CertificateRequest 弥补一个,这样它就可以匹配收件人,但这很棘手。 (或者您想要来自 .NET Core 3.0 的 API)。但如果它真的是 EC 密钥,那么 .NET Core EnvelopedCms 将不适合你,它只支持 RSA / KeyTransfer。
猜你喜欢
  • 2018-08-04
  • 2015-05-24
  • 2021-12-30
  • 2017-11-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-01-03
  • 2010-11-02
相关资源
最近更新 更多