使用 BouncyCastle C# 库从 PGP 加密数据中识别收件人 KeyId
Identify the recipient(s) KeyId from PGP encrypted data using the BouncyCastle C# library
我开发了一个库来对一个或多个收件人执行文件的 PGP signing/encryption 和 decryption/validation。这部分效果很好,可以很好地高效地使用流处理大文件。
PGP 消息交换格式规范的一部分 (RFC 1991) 声明如下:
...
6.7 User ID Packet
Purpose. A user ID packet identifies a user and is associated with a
public or private key.
Definition. A user ID packet is the concatenation of the following
fields:
(a) packet structure field (2 bytes);
(b) User ID string.
The User ID string may be any string of printable ASCII characters.
However, since the purpose of this packet is to uniquely identify an
individual, the usual practice is for the User ID string to consist of
the user's name followed by an e-mail address for that user, the
latter enclosed in angle brackets.
...
我正在创建的应用程序将需要尝试识别用于自动解密文件的适当密钥,以便尽可能减少用户干预。如果无法识别密钥(例如,如果收件人被隐藏),应用程序将提示选择正确的密钥。我正在努力使其尽可能精简。
RFC 建议数据包不是加密数据的一部分,这是有道理的。 PGP 使尝试和识别加密数据的人变得容易。当您在 Kleopatra 将相关密钥添加到其密钥数据库时尝试解密文件时,这一点很明显。在这种情况下,它会提示输入保护密钥的密码。
我的具体问题是:
如何使用 C# BouncyCastle 库读取加密数据的收件人?也就是说,用哪个私钥解密?
我尝试使用 Bouncy Castle GitHub repository 查找示例,但没有找到任何可以证明此特定问题的示例。我也查看了很多 google 这个问题的搜索结果,但都无济于事。
我找到了问题的答案。我认为,如果它是 PGP 规范的一部分,那么它一定是可能的,而且不会太麻烦。因此,我决定仔细检查解密过程以及整个过程中使用的所有对象。
我使用调试器枚举了 PgpEncryptedDataList
中的项目,并找到了 public 密钥的密钥 ID,该密钥在单个 PgpPublicKeyEncryptedData
对象中对其进行了加密。
该对象包含一个名为 KeyId
的 long
类型的 属性。这是我正在寻找与应用程序中存储的密钥相匹配的值。
以下代码片段只是我用来达到 KeyId
属性:
的示例
using (var inputFile = File.OpenRead(@"E:\Staging14d23c-2595abef\testfile.txt.gpg"))
using (var decoderStream = PgpUtilities.GetDecoderStream(inputFile))
{
var objectFactory = new PgpObjectFactory(decoderStream);
var encryptedList = (PgpEncryptedDataList)objectFactory.NextPgpObject();
foreach (var encryptedData in encryptedList.GetEncryptedDataObjects().Cast<PgpPublicKeyEncryptedData>())
{
var keyId = encryptedData.KeyId.ToString("X");
Console.WriteLine($"Encryption Key ID: {keyId}");
}
}
在第一次枚举之后设置断点,您可以检查 encryptedData
变量并观察到类似于以下内容的内容:
所以,经过一番折腾,其实很简单。在解密过程中访问 KeyId
非常简单,您可以自动获取正确的私钥进行解密。
为了完整起见,PGP 通常会为不止一个收件人加密文件。在这种情况下,您会看到不止一个加密数据对象。这并不意味着数据被加密了两次。只有会话密钥。会话密钥被加密 N 次,其中 N 是接收者的数量。这允许每个收件人能够解密 一个 会话密钥,然后继续解密数据。
参考下图显示的两个对象,如您所料,两个 KeyId
属性 :)
此片段来自 PgpDecrypt.cs,它已经查看了加密对象并根据作为参数传入的 PgpSecretKeyRingBundle
检查密钥标识符:
foreach (PgpPublicKeyEncryptedData pked in encryptedDataList.GetEncryptedDataObjects())
{
privateKey = PgpKeyHelper.FindSecretKey(secretKeyRing, pked.KeyId, passPhrase.ToCharArray());
if (privateKey == null)
{
continue;
}
encryptedData = pked;
break;
}
对于希望开始使用 PGP、BouncyCastle 和 C# 的任何人,请 refer to my library 其中包含许多 PGP 函数的汇编。可以更改 PgpDecrypt
class 以自动合并此问题中讨论的密钥发现。
我开发了一个库来对一个或多个收件人执行文件的 PGP signing/encryption 和 decryption/validation。这部分效果很好,可以很好地高效地使用流处理大文件。
PGP 消息交换格式规范的一部分 (RFC 1991) 声明如下:
...
6.7 User ID Packet
Purpose. A user ID packet identifies a user and is associated with a public or private key.
Definition. A user ID packet is the concatenation of the following fields:
(a) packet structure field (2 bytes);
(b) User ID string.
The User ID string may be any string of printable ASCII characters. However, since the purpose of this packet is to uniquely identify an individual, the usual practice is for the User ID string to consist of the user's name followed by an e-mail address for that user, the latter enclosed in angle brackets.
...
我正在创建的应用程序将需要尝试识别用于自动解密文件的适当密钥,以便尽可能减少用户干预。如果无法识别密钥(例如,如果收件人被隐藏),应用程序将提示选择正确的密钥。我正在努力使其尽可能精简。
RFC 建议数据包不是加密数据的一部分,这是有道理的。 PGP 使尝试和识别加密数据的人变得容易。当您在 Kleopatra 将相关密钥添加到其密钥数据库时尝试解密文件时,这一点很明显。在这种情况下,它会提示输入保护密钥的密码。
我的具体问题是:
如何使用 C# BouncyCastle 库读取加密数据的收件人?也就是说,用哪个私钥解密?
我尝试使用 Bouncy Castle GitHub repository 查找示例,但没有找到任何可以证明此特定问题的示例。我也查看了很多 google 这个问题的搜索结果,但都无济于事。
我找到了问题的答案。我认为,如果它是 PGP 规范的一部分,那么它一定是可能的,而且不会太麻烦。因此,我决定仔细检查解密过程以及整个过程中使用的所有对象。
我使用调试器枚举了 PgpEncryptedDataList
中的项目,并找到了 public 密钥的密钥 ID,该密钥在单个 PgpPublicKeyEncryptedData
对象中对其进行了加密。
该对象包含一个名为 KeyId
的 long
类型的 属性。这是我正在寻找与应用程序中存储的密钥相匹配的值。
以下代码片段只是我用来达到 KeyId
属性:
using (var inputFile = File.OpenRead(@"E:\Staging14d23c-2595abef\testfile.txt.gpg"))
using (var decoderStream = PgpUtilities.GetDecoderStream(inputFile))
{
var objectFactory = new PgpObjectFactory(decoderStream);
var encryptedList = (PgpEncryptedDataList)objectFactory.NextPgpObject();
foreach (var encryptedData in encryptedList.GetEncryptedDataObjects().Cast<PgpPublicKeyEncryptedData>())
{
var keyId = encryptedData.KeyId.ToString("X");
Console.WriteLine($"Encryption Key ID: {keyId}");
}
}
在第一次枚举之后设置断点,您可以检查 encryptedData
变量并观察到类似于以下内容的内容:
所以,经过一番折腾,其实很简单。在解密过程中访问 KeyId
非常简单,您可以自动获取正确的私钥进行解密。
为了完整起见,PGP 通常会为不止一个收件人加密文件。在这种情况下,您会看到不止一个加密数据对象。这并不意味着数据被加密了两次。只有会话密钥。会话密钥被加密 N 次,其中 N 是接收者的数量。这允许每个收件人能够解密 一个 会话密钥,然后继续解密数据。
参考下图显示的两个对象,如您所料,两个 KeyId
属性 :)
此片段来自 PgpDecrypt.cs,它已经查看了加密对象并根据作为参数传入的 PgpSecretKeyRingBundle
检查密钥标识符:
foreach (PgpPublicKeyEncryptedData pked in encryptedDataList.GetEncryptedDataObjects())
{
privateKey = PgpKeyHelper.FindSecretKey(secretKeyRing, pked.KeyId, passPhrase.ToCharArray());
if (privateKey == null)
{
continue;
}
encryptedData = pked;
break;
}
对于希望开始使用 PGP、BouncyCastle 和 C# 的任何人,请 refer to my library 其中包含许多 PGP 函数的汇编。可以更改 PgpDecrypt
class 以自动合并此问题中讨论的密钥发现。