Как GPG генерирует отпечаток MD5 по открытому ключу?

Как GPG генерирует отпечаток MD5 по открытому ключу?

У меня есть следующий открытый ключ 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части, которая относится к контрольной сумме CRC, закодированной в Base64. Но если я введу:

$ 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 восьмиоктетный идентификатор ключа состоит из младших 64 бит
открытого модуля ключа RSA.

Отпечаток ключа V3 формируется путем хеширования тела (но не двухоктетной длины) MPI, которые формируют материал ключа (открытый
модуль n, за которым следует показатель e) с MD5. Обратите внимание, что и ключи V3,
и MD5 устарели.

Отпечаток V4 — это 160-битный хэш SHA-1 октета 0x99,
за которым следует двухоктетная длина пакета, за которой следует весь
пакет открытого ключа, начинающийся с поля версии. Идентификатор ключа — это младшие 64 бита отпечатка.

Как это переводится в команду командной строки?

EDIT 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правильно интерпретировать вывод и какие части мне следует объединить, а затем хешировать...

РЕДАКТИРОВАТЬ:Благодаря Йенсу Эрату, с помощью этого небольшого «изучения особенностей OpenPGP» я могу сделать следующий вывод:

ДляКлючи V3 RSA 1024 битключи хэшируются с помощьюMD5, отпечаток пальца вычисляется по129байты, состоящие из128байтыRSA n 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 отпечаток получается путем хеширования первых 272 байтов необработанных данных ключа OpenPGP с помощью SHA1:

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 отпечаток получается путем хеширования первых 528 байтов необработанных данных ключа OpenPGP с помощью SHA1:

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, а только по некоторым (двоичным) его частям.

Для ключа OpenPGP версии 3 вам необходимо сделать следующее:

  1. Анализ пакета открытого ключа OpenPGP
  2. Для RSA извлечь модуль и показатель степени
  3. Объединить их двоичные значения
  4. Рассчитайте хэш-сумму

Для шагов 1 и 2 вы можете положиться на инструмент pgpdump, который может анализировать и выводить числа с использованием -iфлага.

Для ключей версии 4 все становится еще сложнее.

Если вы хотите вычислить хэш-сумму в образовательных целях, я бы рекомендовал pgpdumpвместо этого расширить и использовать весь доступный код парсера, чтобы вы могли напрямую работать с извлеченной информацией. Я почти уверен, что обработка двоичной информации будет проще, чем с чистым кодом оболочки.

ОБНОВЛЯТЬ:Вы использовали неправильные целые числа. Используйте вместо них те, что в строках 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

Связанный контент