c# - 使用 C# 从 p7+p8 文件获取 PEM

标签 c# cryptography bouncycastle pem pkcs#7

简介

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

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

我已经转换了大部分库并与Java版本的作者一起验证了结果。 下一步是让 .Net 库真正可供荷兰公司使用,这也是我过去 2 周一直被困的地方。

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

问题

如何在 C# 中将 p7+p8 文件转换为 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);
}

更新 8-10-2018 根据 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 存储库中)

enter image description here

2018 年 9 月 10 日更新 感谢卡尔找出看似损坏的 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 API 忽略了 SHA-256 OAEP 指令并坚持使用 SHA-1 OAEP,这会导致填充异常。此外,Microsoft API 利用 X509Certificate2,据我所知,它仅支持具有 SHA-1 OAEP 支持的 RsaCryptoServiceProvider。需要更新的 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;
}

关于c# - 使用 C# 从 p7+p8 文件获取 PEM,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/52460001/

相关文章:

python - 是否需要将文件分成 block 进行加密

java - 充气城堡 : Signed Certificate with an existing CA

c# - 如何使黑色变成透明颜色?

javascript - 如何将 id-aes256-GCM 与 Node.JS 加密一起使用? "TypeError: DecipherFinal fail"

C#如何设置声音字节的音量[]

c - 使用 OpenSSL C API 读取 PEM 格式的 RSA key 文件

java - 从未压缩的客户端临时公钥和 Open SSL 静态服务器私钥生成 ECPublicKey/ECPrivateKey 期间出错

java - 在Java中使用Jsch和Sshj的RSA SSH2私钥问题

c# - 如何为自托管的 basic-http WCF 服务打开 Windows 7 防火墙

c# - webdriver - 获取特定 div 中包含的所有类名称的计数