我将以下 GPG 公钥存储在名为publickey.pub
ASCII Armor (Radix-64) 的文件中,并用其编码:
-----BEGIN PGP PUBLIC KEY BLOCK-----
Version: 2.6.3ia
mQCNAzNko/QAAAEEANZ2kpN/oMkz4tqzxvKPZws/XwsD0Y+E5/y7P2DIw4uHS/4N
syQbgkdrZhPBlXDv68DQioHXWsb904qyr7iZB1LC5ItK9MgqlK+Z2mvPqsGbHM8J
+oYib8kf2zJ6HvrYrP7NYB0tN9YYum2ICtx+hIi6aKGXdB1ATA5erwYmu0N9AAUR
tClSYWxmIFMuIEVuZ2Vsc2NoYWxsIDxyc2VAZW5nZWxzY2hhbGwuY29tPokAlQMF
EDNko/QOXq8GJrtDfQEBKVoD/2K/+4pcwhxok+FkuLwC5Pnuh/1oeOYHiKYwx0Z3
p09RLvDtNldr6VD+aL9JltxdPTARzZ8M50UqoF9jMr25GifheFYhilww41OVZA3e
cLXlLgda1+t0vWs3Eg/i2b0arQQDaIq7PeRdjdEDgwnG4xBaqaAqfgxwOXJ+LPWF
hiXZ
=K7lL
-----END PGP PUBLIC KEY BLOCK-----
如果我输入:
$ gpg --with-fingerprint publickey.pub
我得到了钥匙的指纹:
密钥指纹 = 00 C9 21 8E D1 AB 70 37 DD 67 A2 3A 0A 6F 8D A5
那么,GPG 是如何做到的呢?我的意思是,是否有一个命令,我可以不使用而运行它gpg
,但仍然可以获得相同的指纹?例如,使用 SSH,给定一个公钥,我可以执行以下操作:
$ cat ~/.ssh/id_rsa.pub | awk '{print $2}' | base64 -D | md5
这将返回与以下相同的哈希值:
$ ssh-keygen -l -f ~/.ssh/id_rsa.pub
我知道公钥的实际内容应该是:
mQCNAzNko/QAAAEEANZ2kpN/oMkz4tqzxvKPZws/XwsD0Y+E5/y7P2DIw4uHS/4N
syQbgkdrZhPBlXDv68DQioHXWsb904qyr7iZB1LC5ItK9MgqlK+Z2mvPqsGbHM8J
+oYib8kf2zJ6HvrYrP7NYB0tN9YYum2ICtx+hIi6aKGXdB1ATA5erwYmu0N9AAUR
tClSYWxmIFMuIEVuZ2Vsc2NoYWxsIDxyc2VAZW5nZWxzY2hhbGwuY29tPokAlQMF
EDNko/QOXq8GJrtDfQEBKVoD/2K/+4pcwhxok+FkuLwC5Pnuh/1oeOYHiKYwx0Z3
p09RLvDtNldr6VD+aL9JltxdPTARzZ8M50UqoF9jMr25GifheFYhilww41OVZA3e
cLXlLgda1+t0vWs3Eg/i2b0arQQDaIq7PeRdjdEDgwnG4xBaqaAqfgxwOXJ+LPWF
hiXZ
没有最后=K7lL
一部分,即 Base64 编码的 CRC 校验和。但如果我输入:
$ echo -n "mQCNAzNko/QAAAEEANZ2kpN/oMkz4tqzxvKPZws/XwsD0Y+E5/y7P2DIw4uHS/4N
> syQbgkdrZhPBlXDv68DQioHXWsb904qyr7iZB1LC5ItK9MgqlK+Z2mvPqsGbHM8J
> +oYib8kf2zJ6HvrYrP7NYB0tN9YYum2ICtx+hIi6aKGXdB1ATA5erwYmu0N9AAUR
> tClSYWxmIFMuIEVuZ2Vsc2NoYWxsIDxyc2VAZW5nZWxzY2hhbGwuY29tPokAlQMF
> EDNko/QOXq8GJrtDfQEBKVoD/2K/+4pcwhxok+FkuLwC5Pnuh/1oeOYHiKYwx0Z3
> p09RLvDtNldr6VD+aL9JltxdPTARzZ8M50UqoF9jMr25GifheFYhilww41OVZA3e
> cLXlLgda1+t0vWs3Eg/i2b0arQQDaIq7PeRdjdEDgwnG4xBaqaAqfgxwOXJ+LPWF
> hiXZ" | base64 -D | md5
我得到以下输出:
4697e84969da935454c7f2cdc19aaf08
正如你所见,这并不匹配00 C9 21 8E...
检查 RFC 4880 ->https://www.rfc-editor.org/rfc/rfc4880#section-12.2:
对于 V3 密钥,八个八位字节的密钥 ID 由
RSA 密钥的公共模数的低 64 位组成。
V3 密钥的指纹是通过使用 MD5 对构成密钥材料(公共模数 n,后跟指数 e)的 MPI 的主体(但不是两个八位字节长度)进行哈希处理而形成的。请注意,V3 密钥
和 MD5 均已弃用。V4 指纹是八位字节 0x99 的 160 位 SHA-1 哈希,
后跟两个八位字节的数据包长度,然后是
从版本字段开始的整个公钥数据包。密钥 ID 是指纹的低 64 位。
它如何转换为命令行命令?
编辑 1:我正在尝试这样做pgpdump -i
:
$ pgpdump -i publickey.pub
Old: Public Key Packet(tag 6)(141 bytes)
Ver 3 - old
Public key creation time - Mon Apr 28 17:19:48 MSD 1997
Valid days - 0[0 is forever]
Pub alg - RSA Encrypt or Sign(pub 1)
RSA n(1024 bits) - d6 76 92 93 7f a0 c9 33 e2 da b3 c6 f2 8f 67 0b 3f 5f 0b 03 d1 8f 84 e7 fc bb 3f 60 c8 c3 8b 87 4b fe 0d b3 24 1b 82 47 6b 66 13 c1 95 70 ef eb c0 d0 8a 81 d7 5a c6 fd d3 8a b2 af b8 99 07 52 c2 e4 8b 4a f4 c8 2a 94 af 99 da 6b cf aa c1 9b 1c cf 09 fa 86 22 6f c9 1f db 32 7a 1e fa d8 ac fe cd 60 1d 2d 37 d6 18 ba 6d 88 0a dc 7e 84 88 ba 68 a1 97 74 1d 40 4c 0e 5e af 06 26 bb 43 7d
RSA e(5 bits) - 11
Old: User ID Packet(tag 13)(41 bytes)
User ID - Ralf S. Engelschall <[email protected]>
Old: Signature Packet(tag 2)(149 bytes)
Ver 3 - old
Hash material(5 bytes):
Sig type - Generic certification of a User ID and Public Key packet(0x10).
Creation time - Mon Apr 28 17:19:48 MSD 1997
Key ID - 0x0E5EAF0626BB437D
Pub alg - RSA Encrypt or Sign(pub 1)
Hash alg - MD5(hash 1)
Hash left 2 bytes - 29 5a
RSA m^d mod n(1023 bits) - 62 bf fb 8a 5c c2 1c 68 93 e1 64 b8 bc 02 e4 f9 ee 87 fd 68 78 e6 07 88 a6 30 c7 46 77 a7 4f 51 2e f0 ed 36 57 6b e9 50 fe 68 bf 49 96 dc 5d 3d 30 11 cd 9f 0c e7 45 2a a0 5f 63 32 bd b9 1a 27 e1 78 56 21 8a 5c 30 e3 53 95 64 0d de 70 b5 e5 2e 07 5a d7 eb 74 bd 6b 37 12 0f e2 d9 bd 1a ad 04 03 68 8a bb 3d e4 5d 8d d1 03 83 09 c6 e3 10 5a a9 a0 2a 7e 0c 70 39 72 7e 2c f5 85 86 25 d9
-> PKCS-1
我应该如何提取模数和指数?我想我应该对输出的这一部分做些什么:
Hash left 2 bytes - 29 5a
RSA m^d mod n(1023 bits) - 62 bf fb 8a 5c c2 1c 68 93 e1 64 b8 bc 02 e4 f9 ee 87 fd 68 78 e6 07 88 a6 30 c7 46 77 a7 4f 51 2e f0 ed 36 57 6b e9 50 fe 68 bf 49 96 dc 5d 3d 30 11 cd 9f 0c e7 45 2a a0 5f 63 32 bd b9 1a 27 e1 78 56 21 8a 5c 30 e3 53 95 64 0d de 70 b5 e5 2e 07 5a d7 eb 74 bd 6b 37 12 0f e2 d9 bd 1a ad 04 03 68 8a bb 3d e4 5d 8d d1 03 83 09 c6 e3 10 5a a9 a0 2a 7e 0c 70 39 72 7e 2c f5 85 86 25 d9
我试图回应这些十六进制数字的二进制值:
29 5a
(哈希剩下 2 个字节)连接:
62 bf fb 8a 5c c2 1c 68 93 e1 64 b8 bc 02 e4 f9 ee 87 fd 68 78 e6 07 88 a6 30 c7 46 77 a7 4f 51 2e f0 ed 36 57 6b e9 50 fe 68 bf 49 96 dc 5d 3d 30 11 cd 9f 0c e7 45 2a a0 5f 63 32 bd b9 1a 27 e1 78 56 21 8a 5c 30 e3 53 95 64 0d de 70 b5 e5 2e 07 5a d7 eb 74 bd 6b 37 12 0f e2 d9 bd 1a ad 04 03 68 8a bb 3d e4 5d 8d d1 03 83 09 c6 e3 10 5a a9 a0 2a 7e 0c 70 39 72 7e 2c f5 85 86 25 d9
我最终得到的命令是:
$ echo -ne "\x29\x5a\x62\xbf\xfb\x8a\x5c\xc2\x1c\x68\x93\xe1\x64\xb8\xbc\x02\xe4\xf9\xee\x87\xfd\x68\x78\xe6\x07\x88\xa6\x30\xc7\x46\x77\xa7\x4f\x51\x2e\xf0\xed\x36\x57\x6b\xe9\x50\xfe\x68\xbf\x49\x96\xdc\x5d\x3d\x30\x11\xcd\x9f\x0c\xe7\x45\x2a\xa0\x5f\x63\x32\xbd\xb9\x1a\x27\xe1\x78\x56\x21\x8a\x5c\x30\xe3\x53\x95\x64\x0d\xde\x70\xb5\xe5\x2e\x07\x5a\xd7\xeb\x74\xbd\x6b\x37\x12\x0f\xe2\xd9\xbd\x1a\xad\x04\x03\x68\x8a\xbb\x3d\xe4\x5d\x8d\xd1\x03\x83\x09\xc6\xe3\x10\x5a\xa9\xa0\x2a\x7e\x0c\x70\x39\x72\x7e\x2c\xf5\x85\x86\x25\xd9" | md5
它应该输出这些十六进制数字的二进制数据,然后计算该二进制数据的 MD5 哈希值,但我得到的哈希值仍然不同:
6f09f2ac5c5af1c6dd3833e584387103
我知道我做错了,但我没有找到关于如何pgpdump
正确解释输出以及我应该连接哪些部分然后进行哈希处理的信息......
编辑:感谢 Jens Erat,通过这篇“OpenPGP 的指纹研究”,我可以得出结论:
为了V3 密钥 RSA 1024 位密钥哈希MD5,指纹是根据129字节组成的128字节RSA和MPI(从字节偏移量开始14(假设第一个字节位于偏移量1) 导出的原始 OpenPGP 公钥gpg --export $UID
)与1字节,即偏移处的字节144因此省略了2偏移量处的长度字节142和143,正如 RFC 4880 所述。
以下命令使用原始 GPG 数据计算指纹:
gpg --export $UID | xxd -p | tr -d '\n ' | tail \
-c +27 | cut -c -256,261-262 | sed -e 's/[0-9a-fA-F]\{2\}/\\\\x&/g' | while read TMP; do \
echo -ne $TMP; done | md5 | sed -e 's/[0-9a-f]\{2\}/ &/g' | \
awk '{print "\n MD5 fingerprint:"toupper($0)"\n"}'
其中 $UID 是密钥持有者的 UID。
对于 OpenPGP V4 RSA 公钥,情况有所不同:
对于 2048 位 RSA 公钥,通过使用 SHA1 对原始 OpenPGP 密钥数据的前 272 个字节进行哈希处理来获取指纹:
gpg --export $UID | head -c 272 | shasum | grep -Eo "[0-9a-f]+" | sed -e 's/[0-9a-f]\{4\}/ &/g' | \
awk '{print "\n RSA 2048 bit SHA1 fingerprint:"toupper($0)"\n"}'
对于 4096 位 RSA 公钥,通过使用 SHA1 对原始 OpenPGP 密钥数据的前 528 个字节进行哈希处理来获取指纹:
gpg --export $UID | head -c 528 | shasum | \
grep -Eo "[0-9a-f]+" | sed -e 's/[0-9a-f]\{4\}/ &/g' | \
awk '{print "\n RSA 4096 SHA1 fingerprint:"toupper($0)"\n"}'
应该够用了。不管怎么说,用gpgsplit
V4 键似乎更便携。
答案1
对于 OpenPGP 密钥,它不像 SSH 那么简单。指纹不是根据整个 Base64 编码的公钥计算出来的,而是根据它的某些(二进制)部分计算出来的。
对于版本 3 OpenPGP 密钥,您需要执行以下操作:
- 解析OpenPGP公钥包
- 对于 RSA,提取模数和指数
- 连接它们的二进制值
- 计算哈希值
对于步骤 1 和 2,您可以依赖该工具pgpdump
,它可以使用标志解析和输出数字-i
。
对于版本 4 的密钥,它变得更加复杂。
如果您出于教育目的而想计算哈希值,我建议您进行扩展pgpdump
并使用所有可用的解析器代码,这样您就可以直接处理提取的信息。我非常肯定处理二进制信息会比使用纯 shell 代码更容易。
更新:RSA n
您使用了错误的整数。请使用行和中的整数RSA e
。使用标准工具将所有内容放在几行中:
pgpdump -i publickey.pub | \
grep -E '(RSA n|RSA e)' | \
cut -d'-' -f2 | \
tr -d "\n " | \
perl -e 'print pack "H*", <STDIN>' | \
md5sum
pgpdump -i
转储密钥的 MPI,我们将其grep
取出,cut
删除所有不需要的内容,tr
删除所有空格,使用转换为二进制perl
,最后计算md5sum
。
在您提供的密钥上运行:
$ pgpdump -i publickey.pub | \
> grep -E '(RSA n|RSA e)' | \
> cut -d'-' -f2 | \
> tr -d "\n " | \
> perl -e 'print pack "H*", <STDIN>' | \
> md5sum
00c9218ed1ab7037dd67a23a0a6f8da5 -
看起来正是我们正在寻找的东西。
为了完整起见,版本 4 密钥也一样,您需要另一个工具链。我们需要完整的公钥包。要分解 OpenPGP 消息,gpgsplit
这很有用。之后,您可以立即计算sha1sum
文件的:
gpgsplit publickey.pub; sha1sum *.public_key
例如,使用我自己的密钥运行:
$ gpgsplit publickey.pub; sha1sum *.public_key
0d69e11f12bdba077b3726ab4e1f799aa4ff2279 000001-006.public_key