At keyserver.ubuntu.com I look up a public key, say this one, using quux foo as the search string, which when you click on any of the hyperlinks there, say the first one, you get this, the first and last few lines of which I'll show below:
-----BEGIN PGP PUBLIC KEY BLOCK-----
xsFNBFWnnlYBEACxP/X6kj+eOQepGtv5MGXKGvyfzrxWjf1MHP6CX+l932TtoC/a
mraSF4f4HI3i0p9BCbPCTxJSA5AkT0CgF34s3IqpR+vYF8wsdj3yvfvckN/2l72d
.
.
.
koiZ01CsJs1RzL1cNPx6MofudBq4tTwjtzx0hWYmkZqwGas8EQ+BDHHYOmDmflfK
6ovi3RGi9Js0vgSSDeFFVUO5YuIg0KHD
=mPX8
-----END PGP PUBLIC KEY BLOCK-----I use GnuPG, and do the following:
$ gpg --keyserver keyserver.ubuntu.com --search-keys quux fooThe result is as follows:
gpg: data source:
(1) Anand Karthik (foo bar baz quux) <> 4096 bit RSA key 1D94CC5AB7E8F5C6, created: 2015-07-16
Keys 1-1 of 1 for "quux foo". Enter number(s), N)ext, or Q)uit > Here I type in 1, and the following is shown:
gpg: key 1D94CC5AB7E8F5C6: public key "Anand Karthik (foo bar baz quux) <>" imported
gpg: Total number processed: 1
gpg: imported: 1Doing gpg -k shows the key has indeed been successfully imported:
.
.
pub rsa4096 2015-07-16 [SC] B2219477E4935FF8929BEF311D94CC5AB7E8F5C6
uid [ unknown] Anand Karthik (foo bar baz quux) <>
sub rsa4096 2015-07-16 [E]Now I copy what I think is the hash (B2219477E4935FF8929BEF311D94CC5AB7E8F5C6), and attempt to export the key as a text file:
$ gpg --export -a B2219477E4935FF8929BEF311D94CC5AB7E8F5C6The command outputs (shortened in the middle):
-----BEGIN PGP PUBLIC KEY BLOCK-----
mQINBFWnnlYBEACxP/X6kj+eOQepGtv5MGXKGvyfzrxWjf1MHP6CX+l932TtoC/a
mraSF4f4HI3i0p9BCbPCTxJSA5AkT0CgF34s3IqpR+vYF8wsdj3yvfvckN/2l72d
.
.
.
koiZ01CsJs1RzL1cNPx6MofudBq4tTwjtzx0hWYmkZqwGas8EQ+BDHHYOmDmflfK
6ovi3RGi9Js0vgSSDeFFVUO5YuIg0KHD
=Pj2c
-----END PGP PUBLIC KEY BLOCK-----That doesn't look like the key on the website. What am I doing wrong?
11 Answer
It actually looks a lot like the key on the website – only differing in a few characters at the beginning and in the middle1. In fact, if you strip off the Base64 encoding using gpg --dearmor and then binary-compare the resulting files, you will only see these differences:
$ radiff2 orig.gpg new.gpg
0x00000000 c6c14d => 99020d 0x00000000
0x00000210 cd => b4 0x00000210
0x0000024c c2c177 => 890237 0x0000024c
0x00000486 cec14d => b9020d 0x00000486
0x00000696 c2c15f => 89021f 0x00000696There's a pattern. OpenPGP messages are packet-based, and feeding the key through gpg --list-packets or pgpdump will reveal that the "key" consists of several components chained together: a public key packet for the "primary" keypair, followed by user IDs (each followed by signatures), then followed by subkeys (each again followed by signatures). This one key is fairly standard and has five packets...
public key packet: RSA-4096 (key ID 0x1D94CC5AB7E8F5C6) ├─ user ID packet: Anand Karthik (foo bar baz quux) <...> │ └─signature packet: (made by 0x1D94CC5AB7E8F5C6) └─ public subkey packet: RSA-4096 (key ID 0x24E3E92111823AE6) └─signature packet: (made by 0x1D94CC5AB7E8F5C6)...and there are five similar-looking differences, which strongly suggests that they correspond to packet headers. Additionally, looking closer at the gpg --list-packet output, it actually happens to show differences in the packet headers:
$ diff -u <(gpg --list-packets < orig.gpg) <(gpg --list-packets < new.gpg) --- /dev/fd/63 2020-12-16 01:35:38.888515388 +0200 +++ /dev/fd/62 2020-12-16 01:35:38.888515388 +0200 @@ -1,12 +1,12 @@-# off=0 ctb=c6 tag=6 hlen=3 plen=525 new-ctb +# off=0 ctb=99 tag=6 hlen=3 plen=525 :public key packet: version 4, algo 1, created 1437048406, expires 0 pkey[0]: [4096 bits] pkey[1]: [17 bits] keyid: 1D94CC5AB7E8F5C6-# off=528 ctb=cd tag=13 hlen=2 plen=58 new-ctb +# off=528 ctb=b4 tag=13 hlen=2 plen=58 :user ID packet: "Anand Karthik (foo bar baz quux) "-# off=588 ctb=c2 tag=2 hlen=3 plen=567 new-ctb +# off=588 ctb=89 tag=2 hlen=3 plen=567 :signature packet: algo 1, keyid 1D94CC5AB7E8F5C6 version 4, created 1437048406, md5len 0, sigclass 0x13 (...)
The binary encoding used by OpenPGP is not as strict as DER used in X.509, so there can be more than one way to do the same thing. Looking at RFC 4880 (the OpenPGP specification), the first byte (which gpg indicates as "ctb=") is a tag byte which indicates the packet format, which has two versions in itself – and this causes the primary difference:
- The original key has bit 6 set in its packet tags, indicating that Hockeypuck keyserver software writes the new-format tag byte (which GnuPG indicates as "new-ctb").
- The exported key has bit 6 cleared, indicating that GnuPG writes the old-format tag byte.
The "new" format allows six bits for the actual type (allowing 64 different packet types), while the "old" one only reserves four bits (limiting to 16 packet types). Looking at the source code, it seems GnuPG generates old-format tags whenever it can get away with it, only switching to new-format for types ≥16, thus maximizing compatibility with old PGP implementations that predate RFC 4880.
The two tag versions also encode the "packet length" field differently, which is what causes differences in bytes 2–3 of every header.
1 Note that the last line of PGP armored messages (the one beginning with =) is a checksum for detecting corruption (24-bit CRC) so it will naturally be quite different if even just one byte changes. This checksum is part of the ASCII-armor format – it is not part of the encoded data, and will not show up in a binary diff.