3

I am trying to get a public PGP key from a keyring created by GnuPG using the BouncyCastle C# library. I've gotten it to semi-work by using the following code. The problem is that the public key it outputs is about half the length of the real one and the last few bytes are also different. I'm just trying to get the real key.

UPDATE: Something interesting to note is that the keyring I generated only had one public key, yet I'm getting two out of bouncycastle. I also found that if you insert the second key into the first a few characters from the end, it produces almost the original key. Only a few chars at the end are different. So why are there two keys and why does this occur? What am I missing?

Is the GnuPG keyring not compatible?

Also note that the code shown here only gets the last key. I'm now adding each to a list.

Here is my code:

public static string ReadKey(string pubkeyFile)
{
    string theKey;
    Stream fs = File.OpenRead(pubkeyFile);

    //
    // Read the public key rings
    //
    PgpPublicKeyRingBundle pubRings = new PgpPublicKeyRingBundle(PgpUtilities.GetDecoderStream(fs));
    fs.Close();

    foreach (PgpPublicKeyRing pgpPub in pubRings.GetKeyRings())
    {
        pgpPub.GetPublicKey();

        foreach (PgpPublicKey pgpKey in pgpPub.GetPublicKeys())
        {
            //AsymmetricKeyParameter pubKey = pgpKey.GetKey();
            //SubjectPublicKeyInfo k = SubjectPublicKeyInfoFactory.CreateSubjectPublicKeyInfo(pubKey);
            //byte[] keyData = k.ToAsn1Object().GetDerEncoded();
            //byte[] keyData = k.GetEncoded();
            byte[] keyData = pgpKey.GetEncoded();
            theKey = Convert.ToBase64String(keyData);
        }
    }
    return theKey;
}

Here is the actual public key:

-----BEGIN PGP PUBLIC KEY BLOCK-----
Version: GnuPG v2.0.20 (MingW32)

mQENBFP2z94BCADKfQT9DGHm4y/VEAYGL7XiUavbv+aE7D2OZ2jCbwnx7BYzQBu8
63v5qYe7oH0oBOiw67VaQSjS58fSBAE8vlTkKjvRAscHJNUX9qZrQoRtpMSnrK7N
Ca9N2ptvof7ykF1TAgbxDSSnhwysVznYc7mx76BO6Qx8KChqEd0Yp3w2U89YkUqN
qdzjB7ZIhj5hDM9f4eyHwsz0uZgyqLKK5VgNj6dHVmOHZt6+RIydRC2lGfocWKM8
loPkk6GiSX9sdEm6GXxi7gV/Q3Jr0G099AFg57cWyj1eO6NC8YHLgBHwrB1IkFwi
J0x5IHZssy/XleQ1i1izc3ntWiiH24powuAhABEBAAG0H3N5bmFwczMgPHN5bmFw
czNAc2FmZS1tYWlsLm5ldD6JATkEEwECACMFAlP2z94CGwMHCwkIBwMCAQYVCAIJ
CgsEFgIDAQIeAQIXgAAKCRD944Hz1MHUYP+AB/4roauazFR5lDrJBFB0YoH4VFKM
28IJtuy6OThg3cxhqI/N74sZoxtB90QQk4lcshdpwD7CIe9TCKrnhWokIdm4N91m
TGDmW7iIeM3kcPp3mj9/7hGOetESuz9JxhBQ0aHAXYk5LdHeDKyRg1KL3JvWrJ27
fioDoLLpxxdudSd2nJLhi0hAaHKnkLVl98r37AwxTigGj+J2rN47D+UepJraf8je
eZrY/RfwKJVleF1KYPIgduwX3jdiABrI4EsZP/CdbEWTvmmkFFtD4clSMsmqaXPT
a3VeaL/saScBPL93tDsjqCddcgW28hsnhzoJ7TM78j2zNcTXZjK8/tNCDnShuQEN
BFP2z94BCADmCAMIpOp518ywUlG5Pze5HdpgGiQF26XzwxUt3mPAMXBUQ7vqRMD/
zNagPXKthp/p4t0jRoFwFwF+7CqRrxkv2Rrj5OqDD7JqETY5nfRZ0Hvfi4cPkf2g
S17SVI4LSFQ/v/sISNNiI3Bo/xvpOeK+Af087j4BEe8vjFuyzf08HCglKoL6WAp8
5+Wc2vj+7EbH61YloKKNugq34AyuNh1QYml6LI04b2KR0b/qXTW8UqLvrh4YGaOp
k80l7DpBmKgGtXn8JFfU9V3sGCscSnfzDvKjqpmtKXiJFxO2pyPCN5jRKfGMOSyA
fZ21NIrBJER/WvuIAls8Tikk+wKRKXrpABEBAAGJAR8EGAECAAkFAlP2z94CGwwA
CgkQ/eOB89TB1GDDEAf+OA9hgb3FLbEtcNvkUl9wTtLaxr9nAsBowofNEITH96hV
w4i6em9Rjg29/+4JrnDhibuhsFr/F8uKoj+iZGFw2NpXHYI6yS+BLbuVj8jOkYAy
Gq34HMNWXuS1Nr4VHOxKbKmmLu8YhdYRk2KF9fPI2Qj376C69W90R/LHByCrcCg7
xmqAvO9a8Eac7Rk+Fc+5NKVw9D1rP7MqZGgIQQoh8jLiI2MblvEEahwNxA9AYs8U
PpMD0pdo93wxXIYuKc40MF4yFL9LfpPxDnf373dbYQjk3pNThQ5RagIgLNEhRow4
5x/1wcO6FMx5a/irQXnJ2o1XYRvznBeCsoyOAYbikA==
=r3Qj
-----END PGP PUBLIC KEY BLOCK-----

Here is NEW KEY produced by BouncyCastle (sorry can't help formatting):

mQENBFP2z94BCADKfQT9DGHm4y/VEAYGL7XiUavbv+aE7D2OZ2jCbwnx7BYzQBu863v5qYe7oH0oBOiw67VaQSjS58fSBAE8vlTkKjvRAscHJNUX9qZrQoRtpMSnrK7NCa9N2ptvof7ykF1TAgbxDSSnhwysVznYc7mx76BO6Qx8KChqEd0Yp3w2U89YkUqNqdzjB7ZIhj5hDM9f4eyHwsz0uZgyqLKK5VgNj6dHVmOHZt6+RIydRC2lGfocWKM8loPkk6GiSX9sdEm6GXxi7gV/Q3Jr0G099AFg57cWyj1eO6NC8YHLgBHwrB1IkFwiJ0x5IHZssy/XleQ1i1izc3ntWiiH24powuAhABEBAAG0H3N5bmFwczMgPHN5bmFwczNAc2FmZS1tYWlsLm5ldD6JATkEEwECACMFAlP2z94CGwMHCwkIBwMCAQYVCAIJCgsEFgIDAQIeAQIXgAAKCRD944Hz1MHUYP+AB/4roauazFR5lDrJBFB0YoH4VFKM28IJtuy6OThg3cxhqI/N74sZoxtB90QQk4lcshdpwD7CIe9TCKrnhWokIdm4N91mTGDmW7iIeM3kcPp3mj9/7hGOetESuz9JxhBQ0aHAXYk5LdHeDKyRg1KL3JvWrJ27fioDoLLpxxdudSd2nJLhi0hAaHKnkLVl98r37AwxTigGj+J2rN47D+UepJraf8jeeZrY/RfwKJVleF1KYPIgduwX3jdiABrI4EsZP/CdbEWTvmmkFFtD4clSMsmqaXPTa3VeaL/saScBPL93tDsjqCddcgW28hsnhzoJ7TM78j2zNcTXZjK8/tNCDnShsAIAA7kBDQRT9s/eAQgA5ggDCKTqedfMsFJRuT83uR3aYBokBdul88MVLd5jwDFwVEO76kTA/8zWoD1yrYaf6eLdI0aBcBcBfuwqka8ZL9ka4+Tqgw+yahE2OZ30WdB734uHD5H9oEte0lSOC0hUP7/7CEjTYiNwaP8b6TnivgH9PO4+ARHvL4xbss39PBwoJSqC+lgKfOflnNr4/uxGx+tWJaCijboKt+AMrjYdUGJpeiyNOG9ikdG/6l01vFKi764eGBmjqZPNJew6QZioBrV5/CRX1PVd7BgrHEp38w7yo6qZrSl4iRcTtqcjwjeY0SnxjDksgH2dtTSKwSREf1r7iAJbPE4pJPsCkSl66QARAQABiQEfBBgBAgAJBQJT9s/eAhsMAAoJEP3jgfPUwdRgwxAH/jgPYYG9xS2xLXDb5FJfcE7S2sa/ZwLAaMKHzRCEx/eoVcOIunpvUY4Nvf/uCa5w4Ym7obBa/xfLiqI/omRhcNjaVx2COskvgS27lY/IzpGAMhqt+BzDVl7ktTa+FRzsSmyppi7vGIXWEZNihfXzyNkI9++guvVvdEfyxwcgq3AoO8ZqgLzvWvBGnO0ZPhXPuTSlcPQ9az+zKmRoCEEKIfIy4iNjG5bxBGocDcQPQGLPFD6TA9KXaPd8MVyGLinONDBeMhS/S36T8Q539+93W2EI5N6TU4UOUWoCICzRIUaMOOcf9cHDuhTMeWv4q0F5ydqNV2Eb85wXgrKMjgGG4pCwAgAD

Still not the same. Put both keys in notepad and search the "tNCDnS", after that is where they change.

Thanks for sticking with me. I've gotten far enough with this that I don't want to scrap the whole code for some shitty encryption.

Synaps3
  • 1,597
  • 2
  • 15
  • 23
  • Have you tried `getPublicKeyPacket` instead? Maybe you are missing some data such as trust information and/or signatures over the key. – Maarten Bodewes Aug 24 '14 at 23:11
  • Please help!!! There is zero documentation on this library and it is as unintuitive as possible. I need some help. I need to finish this project. I can't even find getPublicKeyPacket. – Synaps3 Aug 29 '14 at 21:24
  • Darn, usually the C# lib is a copy of the Java one... I would have to set up an entire project myself to help you with this; that's a bit too much... Try the bouncy-dev mailinglist! – Maarten Bodewes Aug 29 '14 at 21:45
  • Guess I haven't been around long enough. Didn't know mailing list ~= forum. Thanks for letting me know. – Synaps3 Sep 01 '14 at 22:51

1 Answers1

5

You may have been a victim of BouncyCastle's somewhat uncommon usage of the term "keyring". Here is BouncyCastle's usage of three related terms:

PgpPublicKey: This is the public part of a single mathematical key, in PGP format. It does not contain any subkeys.

PgpPublicKeyRing: This is a cryptographic key with its subkeys. Other programs usually refer to this as a PGP Key.

PgpPublicKeyRingBundle: This is any number of PgpPublicKeyRings (as defined in BouncyCastle). Other programs usually refer to this as a Public Key Ring (without bundle).

You iterated through the keyring to find all PgpPublicKey objects. You decoded them and return the last and only the last of them as a string. If you paste the input and output strings to pgpdump.net (you have to add the BEGIN and END headers to the output), you see that you lose the subkey in this procedure. Use the PgpPublicKeyRing's GetEncoded() method instead. This should retain all information and is also easier :-).

Additionally, PGP's Radix-encoding is a little bit more than just Base64-encoding. A lot of programs and libraries, including BC, ignores whether there are PGP headers and footers and also the CRC checksum that Radix-encoding includes as opposed to simple Base64. However, GPG is more strict and seems not to accept ASCII armors without CRC checksum. Therefore, you have to use the binary key (which is the byte[] keyData in your problem code) or create a proper PGP ASCII armor. I have edited the following code based on your code to implement the latter using BC's ArmoredOutputStream:

public static string ReadKey(string pubkeyFile)
{
    Stream fs = File.OpenRead(pubkeyFile);

    //
    // Read the public key rings
    //
    PgpPublicKeyRingBundle pubRings = new PgpPublicKeyRingBundle(PgpUtilities.GetDecoderStream(fs));
    fs.Close();

    foreach (PgpPublicKeyRing pgpPub in pubRings.GetKeyRings())
    {
            using (MemoryStream ms = new MemoryStream())
            {
                using (ArmoredOutputStream aos = new ArmoredOutputStream(ms))
                    pgpPub.Encode(aos);
                return System.Text.Encoding.ASCII.GetString(ms.ToArray());
            }
    }
    return null;
}

EDIT: The following is a complete program with no external dependencies. It works for me, i.e. outputs "Same!". Please run that exact program and check whether it outputs "Same!" or "Difference!". If it outputs "Same!", then you can use it to correct your own program code:

using System;
using System.IO;
using Org.BouncyCastle.Bcpg.OpenPgp;

class Program
{
    private const string PGP_OVERFLOW_KEYBODY =
    "mQENBFP2z94BCADKfQT9DGHm4y/VEAYGL7XiUavbv+aE7D2OZ2jCbwnx7BYzQBu8\r\n" +
    "63v5qYe7oH0oBOiw67VaQSjS58fSBAE8vlTkKjvRAscHJNUX9qZrQoRtpMSnrK7N\r\n" +
    "Ca9N2ptvof7ykF1TAgbxDSSnhwysVznYc7mx76BO6Qx8KChqEd0Yp3w2U89YkUqN\r\n" +
    "qdzjB7ZIhj5hDM9f4eyHwsz0uZgyqLKK5VgNj6dHVmOHZt6+RIydRC2lGfocWKM8\r\n" +
    "loPkk6GiSX9sdEm6GXxi7gV/Q3Jr0G099AFg57cWyj1eO6NC8YHLgBHwrB1IkFwi\r\n" +
    "J0x5IHZssy/XleQ1i1izc3ntWiiH24powuAhABEBAAG0H3N5bmFwczMgPHN5bmFw\r\n" +
    "czNAc2FmZS1tYWlsLm5ldD6JATkEEwECACMFAlP2z94CGwMHCwkIBwMCAQYVCAIJ\r\n" +
    "CgsEFgIDAQIeAQIXgAAKCRD944Hz1MHUYP+AB/4roauazFR5lDrJBFB0YoH4VFKM\r\n" +
    "28IJtuy6OThg3cxhqI/N74sZoxtB90QQk4lcshdpwD7CIe9TCKrnhWokIdm4N91m\r\n" +
    "TGDmW7iIeM3kcPp3mj9/7hGOetESuz9JxhBQ0aHAXYk5LdHeDKyRg1KL3JvWrJ27\r\n" +
    "fioDoLLpxxdudSd2nJLhi0hAaHKnkLVl98r37AwxTigGj+J2rN47D+UepJraf8je\r\n" +
    "eZrY/RfwKJVleF1KYPIgduwX3jdiABrI4EsZP/CdbEWTvmmkFFtD4clSMsmqaXPT\r\n" +
    "a3VeaL/saScBPL93tDsjqCddcgW28hsnhzoJ7TM78j2zNcTXZjK8/tNCDnShuQEN\r\n" +
    "BFP2z94BCADmCAMIpOp518ywUlG5Pze5HdpgGiQF26XzwxUt3mPAMXBUQ7vqRMD/\r\n" +
    "zNagPXKthp/p4t0jRoFwFwF+7CqRrxkv2Rrj5OqDD7JqETY5nfRZ0Hvfi4cPkf2g\r\n" +
    "S17SVI4LSFQ/v/sISNNiI3Bo/xvpOeK+Af087j4BEe8vjFuyzf08HCglKoL6WAp8\r\n" +
    "5+Wc2vj+7EbH61YloKKNugq34AyuNh1QYml6LI04b2KR0b/qXTW8UqLvrh4YGaOp\r\n" +
    "k80l7DpBmKgGtXn8JFfU9V3sGCscSnfzDvKjqpmtKXiJFxO2pyPCN5jRKfGMOSyA\r\n" +
    "fZ21NIrBJER/WvuIAls8Tikk+wKRKXrpABEBAAGJAR8EGAECAAkFAlP2z94CGwwA\r\n" +
    "CgkQ/eOB89TB1GDDEAf+OA9hgb3FLbEtcNvkUl9wTtLaxr9nAsBowofNEITH96hV\r\n" +
    "w4i6em9Rjg29/+4JrnDhibuhsFr/F8uKoj+iZGFw2NpXHYI6yS+BLbuVj8jOkYAy\r\n" +
    "Gq34HMNWXuS1Nr4VHOxKbKmmLu8YhdYRk2KF9fPI2Qj376C69W90R/LHByCrcCg7\r\n" +
    "xmqAvO9a8Eac7Rk+Fc+5NKVw9D1rP7MqZGgIQQoh8jLiI2MblvEEahwNxA9AYs8U\r\n" +
    "PpMD0pdo93wxXIYuKc40MF4yFL9LfpPxDnf373dbYQjk3pNThQ5RagIgLNEhRow4\r\n" +
    "5x/1wcO6FMx5a/irQXnJ2o1XYRvznBeCsoyOAYbikA==";

    static void Main(string[] args)
    {
        string parsedKey = ReadKeyDirectly(PGP_OVERFLOW_KEYBODY);
        if (parsedKey != PGP_OVERFLOW_KEYBODY.Replace("\r\n",""))
            Console.WriteLine("Difference!");
        else
            Console.WriteLine("Same!");
    }

    public static string ReadKeyDirectly(string stringKeyData)
    {
        Stream fs = new MemoryStream(System.Text.Encoding.ASCII.GetBytes(stringKeyData));
        fs.Seek(0, SeekOrigin.Begin);

        PgpPublicKeyRingBundle pubRings = new PgpPublicKeyRingBundle(PgpUtilities.GetDecoderStream(fs));

        foreach (PgpPublicKeyRing pubRing in pubRings.GetKeyRings())
            return Convert.ToBase64String(pubRing.GetEncoded());
        return null;
    }
}
Froggy
  • 339
  • 2
  • 15
  • This just returns all the keys, but the above problem still exists. – Synaps3 Oct 03 '14 at 03:12
  • Ok, so I had misunderstood your question. I thought that you had posted your input, but it was only the output. I corrected my answer accordingly. If you still have questions, please post your input. – Froggy Oct 05 '14 at 18:06
  • Thanks for the description. This code still doesn't work though. I only get half of the actual public key posted above, the rest is something else. – Synaps3 Oct 07 '14 at 01:05
  • If I use the key you posted as "the actual public key" as content for the file that is the argument for my ReadKey function, the return value is bit by bit identical to "the actual public key" -- except for the header and footer rows and the five checksum characters. Is this different for you? Then the .NET framework version or your BC version causes the problem. I suspect that I still misunderstand you somehow, though. Also, have you tried to post your keys to pgpdump.net to see what PGP packets are inside? Your "NEW KEY" output contains additional trust packets. – Froggy Oct 08 '14 at 09:32
  • What I posted is whats different for me. I tried the pgpdump and found the only difference was two trust packets like you mentioned. What doesn't make sense is that the site reports these as being 2 bytes long each and clearly, there is way more than that difference. I copied and pasted, reformatted, and then tried adding the key back into GnuPG. "No valid keys to be imported". Maybe it's because I'm reading a keyring file rather than the key directly? I doubt it's the version, but if it matters Im running .NET 2.0 with BC 1.7.0.5. I also got a ILLEGAL RADIX64 CHARACTER at the end of pgpdump. – Synaps3 Oct 08 '14 at 12:28
  • Also, all public keys end with = and then four characters. I'm thinking this is a bug in bcpg. Next thing to try is create a public key in bcpg and see if it will load into GnuPG. Instead of generating in GnuPG, reading with bcpg and back into GnuPG like I was doing. That won't really solve the issue though because there's not much of a point if I can't use keys from other programs with this. – Synaps3 Oct 08 '14 at 13:01
  • 1
    It works for me, so it's not a general bc bug. An ASCII-armored PGP key consists of a header (-----BEGIN...), optional comments, a blank line, the Base64-encoded key, a checksum (=ABCD) and a footer(-----END...). Our BC code only outputs the base64-encoded key, but most software still accepts that. If you Base64-decode the output, you have a valid PGP-key without ASCII-armor. – Froggy Oct 09 '14 at 13:06
  • I added a complete program to my answer above. Please copy & paste that program and run it. It has no external dependencies, so if your output is not "Same!" (like on my machine), then there is really something different between our machines. Otherwise, it should help you identify the problem in your code. The hard-coded key is the one you posted in your question. – Froggy Oct 09 '14 at 13:31
  • Again, thanks for keeping me updated. I took a break from this project for a bit. I got the same results running your program. I'm very sure at this point that the problem exists in the keyring file itself (or BCs ability to read a keyring), not the key. Would you mind doing me a favor and try running my code with a PGP keyring file (not just a key). I've produced my keyring with GnuPG. All I have done was downloaded GnuPG and generated a new keyring. I then navigated to my [username]\appdata\roaming\gnupg and copied the pubring.gpg to my C# project location and loaded it with Bouncy. – Synaps3 Oct 15 '14 at 10:23
  • . . . After doing that see if it produces a bad key similar to mine. To verify the validity (other than simply looking at it), I tried to import back into GnuPG. You can also try with my keyring file here: http://www.filedropper.com/pubring – Synaps3 Oct 15 '14 at 10:47
  • I have tried it with your keyring and I think that I understand the problem now. GPG is quite strict about the ASCII armor and therefore did not accept the key. I edited the code in my answer to create a valid ASCII armor, this key can now be imported with GPG. – Froggy Oct 16 '14 at 12:08
  • Thank you! That works. One last question: When I use pgpPub.GetPublicKeys(), it returns two keys with the exact same properties. Why two? Can I just ignore the subsequent ones and break the foreach? I'm trying to get the KeyID and Fingerprint (which I know how to do, it's just giving me two). – Synaps3 Oct 18 '14 at 00:51
  • Mathematically, "the" key you posted is in fact a bundle of two keys -- one primary key and one subkey. With some exceptions, PGP traditionally uses the primary key for signing and the subkey for signatures. You can see this with pgpdump in the key flags field. You shouldn't separate the two keys, but if you do it right, the resulting key may be used only either for signature verification (first key) or encryption (second key). Therefore they also have different KeyIDs -- e.g. if you encrypt to the "key bundle", gpg and the like will use the subkey and its keyid. – Froggy Oct 23 '14 at 10:46
  • @Froggy Hey thre Froggy! I'm importing a SecretKeyRingBundle from which I take the first found SecretKeyRing and search for the key that is going to be used for encryption (i.e it has key flags set for encryption). So far so good, but now when I try to extract the public key of the encryption sub-key into ASCII armor, the payload starts with a BEGIN PGP MESSAGE instead of BEGIN PUBLIC KEY etc, and I can't import it anywhere. I'm soo confused. If I do the same for the certification sub-key - it works, but for the encryption one? Nope. – SpiritBob May 16 '22 at 13:36
  • @SpiritBob: I don't know what's going on, but if you provide some code, we may find out. A "PGP MESSAGE" indicates it is encrypted data, not a key. – Froggy May 31 '22 at 12:31