Como o GPG gera uma impressão digital MD5 com uma chave pública?

Como o GPG gera uma impressão digital MD5 com uma chave pública?

Tenho a seguinte chave pública GPG armazenada dentro de um arquivo chamado publickey.pube codificado em 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-----

Se eu digitar:

$ gpg --with-fingerprint publickey.pub

Recebo a impressão digital da chave:

Impressão digital da chave = 00 C9 21 8E D1 AB 70 37 DD 67 A2 3A 0A 6F 8D A5

Agora, como o GPG faz isso? Quer dizer, existe um comando que eu possa executar sem usar gpge ainda assim obter a mesma impressão digital? Com o SSH, por exemplo, dada uma chave pública posso fazer o seguinte:

$ cat ~/.ssh/id_rsa.pub | awk '{print $2}' | base64 -D | md5

E isso retornará o mesmo hash que:

$ ssh-keygen -l -f ~/.ssh/id_rsa.pub

Eu sei que o conteúdo real da chave pública deveria ser:

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

Sem a última =K7lLparte, que se refere ao checksum CRC codificado em Base64. Mas se eu digitar:

$ 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

Eu recebo a seguinte saída:

4697e84969da935454c7f2cdc19aaf08

O que, como você pode ver, não corresponde00 C9 21 8E...

Verificando o RFC 4880 ->https://www.rfc-editor.org/rfc/rfc4880#section-12.2:

Para uma chave V3, o ID da chave de oito octetos consiste nos 64 bits inferiores do
módulo público da chave RSA.

A impressão digital de uma chave V3 é formada pelo hash do corpo (mas não do comprimento de dois octetos) dos MPIs que formam o material da chave (
módulo público n, seguido pelo expoente e) com MD5. Observe que as chaves V3
e MD5 estão obsoletas.

Uma impressão digital V4 é o hash SHA-1 de 160 bits do octeto 0x99,
seguido pelo comprimento do pacote de dois octetos, seguido por todo o
pacote de chave pública começando com o campo de versão. O Key ID são os 64 bits de ordem inferior da impressão digital.

Como isso se traduz em um comando de linha de comando?

EDIT 1: Estou tentando fazer isso com 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

Como devo extrair módulo e expoente? Acho que deveria fazer algo com esta parte da saída:

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  

Tentei ecoar os valores binários desses dígitos hexadecimais:

29 5a(Hash deixou 2 bytes) concatenado com: 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

E o comando que acabei foi:

$ 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

O que deve gerar os dados binários desses dígitos hexadecimais e, em seguida, calcular o hash MD5 nesses dados binários, mas o hash que recebo ainda é diferente:

6f09f2ac5c5af1c6dd3833e584387103

Eu sei que estou fazendo errado, mas não encontrei informações sobre como interpretar a pgpdumpsaída corretamente e quais partes do que devo concatenar e depois fazer o hash ...

EDITAR:Graças a Jens Erat, com este pequeno "geeking das impressões digitais do OpenPGP" posso concluir que:

ParaChaves V3 RSA 1024 bitschaves hash comMD5, a impressão digital é computada contra129bytes compostos por128bytes doRSA e MPI(que começa no deslocamento de bytes14(desde que o primeiro byte esteja no deslocamento1) da chave pública OpenPGP bruta exportada com gpg --export $UID) concatenada com1byte que é o byte no deslocamento144e, portanto, omitindo o2bytes de comprimento no deslocamento142e143, como diz a RFC 4880.

O comando a seguir calcula a impressão digital usando dados GPG brutos e:

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"}' 

Onde $UID é o UID do detentor da chave.

Para chaves públicas OpenPGP V4 RSA, a história é diferente:

Para chaves públicas RSA de 2.048 bits, a impressão digital é obtida fazendo hash dos primeiros 272 bytes dos dados brutos da chave OpenPGP com 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"}'

Para chaves públicas RSA de 4.096 bits, a impressão digital é obtida fazendo hash dos primeiros 528 bytes dos dados brutos da chave OpenPGP com 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"}'

Deve ser suficiente. De qualquer forma, usar gpgsplitcom teclas V4 parece ser mais portátil.

Responder1

Para chaves OpenPGP, não é tão fácil quanto com SSH. A impressão digital não é calculada a partir de toda a chave pública codificada em Base64, mas apenas em algumas partes (binárias) dela.

Para uma chave OpenPGP versão 3, o que você precisa fazer é:

  1. Analise o pacote de chave pública OpenPGP
  2. Para RSA, extraia módulo e expoente
  3. Concatene seus valores binários
  4. Calcule o hashsum

Para as etapas 1 e 2, você pode contar com a ferramenta pgpdump, que pode analisar e gerar os números usando o -isinalizador.

Para as chaves da versão 4, fica ainda mais complicado.

Se você quiser calcular o hashsum para fins educacionais, recomendo estender pgpdumpe usar todo o código do analisador disponível, para que você possa trabalhar diretamente nas informações extraídas. Tenho certeza de que lidar com as informações binárias será mais fácil do que com código shell puro.

ATUALIZAR:Você usou os números inteiros errados. Use aqueles em linhas RSA ne RSA eem vez disso. Reunindo tudo em poucas linhas usando ferramentas padrão:

pgpdump -i publickey.pub | \
grep -E '(RSA n|RSA e)' | \
cut -d'-' -f2 | \
tr -d "\n " | \
perl -e 'print pack "H*", <STDIN>' | \
md5sum

pgpdump -idespeja os MPIs da chave, que grepretiramos cutde tudo que não precisamos, treliminamos todos os espaços em branco, convertemos para binário usando perle finalmente calculamos o arquivo md5sum.

Executando na chave fornecida:

$ pgpdump -i publickey.pub | \
> grep -E '(RSA n|RSA e)' | \
> cut -d'-' -f2 | \
> tr -d "\n " | \
> perl -e 'print pack "H*", <STDIN>' | \
> md5sum
00c9218ed1ab7037dd67a23a0a6f8da5  -

Parece ser o que procuramos.

Por uma questão de integridade, o mesmo para as chaves da versão 4, onde você precisa de outro conjunto de ferramentas. Precisamos do pacote completo de chave pública. Para decompor uma mensagem OpenPGP, gpgsplité útil. Depois, você pode calcular imediatamente o sha1sumvalor do arquivo:

gpgsplit publickey.pub; sha1sum *.public_key

Por exemplo, executando na minha própria chave:

$ gpgsplit publickey.pub; sha1sum *.public_key
0d69e11f12bdba077b3726ab4e1f799aa4ff2279  000001-006.public_key

informação relacionada