¿Cómo genera GPG una huella digital MD5 dada una clave pública?

¿Cómo genera GPG una huella digital MD5 dada una clave pública?

Tengo la siguiente clave pública GPG almacenada dentro de un archivo llamado publickey.puby codificado en 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-----

Si escribo:

$ gpg --with-fingerprint publickey.pub

Obtengo la huella digital de la clave:

Huella digital clave = 00 C9 21 8E D1 AB 70 37 DD 67 A2 3A 0A 6F 8D A5

Ahora bien, ¿cómo lo hace GPG? Quiero decir, ¿hay algún comando que pueda ejecutar sin usarlo gpgy seguir obteniendo la misma huella digital? Con SSH, por ejemplo, dada una clave pública puedo hacer lo siguiente:

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

Y eso devolverá el mismo hash que:

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

Sé que el contenido real de la clave pública debería 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

Sin la última =K7lLparte, que hace referencia a la suma de comprobación CRC codificada en Base64. Pero si escribo:

$ 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

Obtengo el siguiente resultado:

4697e84969da935454c7f2cdc19aaf08

Lo cual, como puedes ver, no coincide.00 C9 21 8E...

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

Para una clave V3, la ID de clave de ocho octetos consta de los 64 bits bajos del
módulo público de la clave RSA.

La huella digital de una clave V3 se forma aplicando hash al cuerpo (pero no a la longitud de dos octetos) de los MPI que forman el material de la clave (
módulo público n, seguido del exponente e) con MD5. Tenga en cuenta que tanto las claves V3
como MD5 están en desuso.

Una huella digital V4 es el hash SHA-1 de 160 bits del octeto 0x99,
seguido de la longitud del paquete de dos octetos, seguido del
paquete de clave pública completo comenzando con el campo de versión. La ID de clave son los 64 bits de orden inferior de la huella digital.

¿Cómo se traduce en un comando de línea de comando?

EDITAR 1: estoy intentando hacer eso con 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

¿Cómo debo extraer el módulo y el exponente? Supongo que debería hacer algo con esta parte del resultado:

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  

Intenté hacer eco de los valores binarios de esos dígitos hexadecimales:

29 5a(Hash dejó 2 bytes) concatenado con: 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

Y el comando con el que terminé fue:

$ 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

Lo que debería generar los datos binarios de esos dígitos hexadecimales y luego calcular el hash MD5 en esos datos binarios, pero el hash que obtengo sigue siendo diferente:

6f09f2ac5c5af1c6dd3833e584387103

Sé que lo estoy haciendo mal, pero no encontré información sobre cómo interpretar el pgpdumpresultado correctamente y qué partes de lo que debo concatenar y luego aplicar hash...

EDITAR:Gracias a Jens Erat, con este pequeño "geek de las huellas dactilares de OpenPGP" puedo concluir que:

ParaClaves V3 RSA 1024 bitsclaves hash conMD5, la huella digital se computa contra129bytes formados por128bytes de laRSA y MPI(que comienza en el desplazamiento de bytes14(siempre que el primer byte esté en desplazamiento1) de la clave pública OpenPGP sin procesar exportada con gpg --export $UID) concatenada con1byte que es el byte en el desplazamiento144y por lo tanto omitiendo el2bytes de longitud en el desplazamiento142y143, como dice RFC 4880.

El siguiente comando calcula la huella digital utilizando datos GPG sin procesar y:

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

Donde $UID es el UID del titular de la clave.

Para las claves públicas OpenPGP V4 RSA, la historia es diferente:

Para claves públicas RSA de 2048 bits, la huella digital se obtiene aplicando hash a los primeros 272 bytes de los datos de la clave OpenPGP sin procesar con 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 claves públicas RSA de 4096 bits, la huella digital se obtiene aplicando hash a los primeros 528 bytes de los datos de la clave OpenPGP sin procesar con 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"}'

Debería ser suficiente. De todos modos, usarlo gpgsplitcon teclas V4 parece ser más portátil.

Respuesta1

Para las claves OpenPGP, no es tan fácil como con SSH. La huella digital no se calcula a partir de toda la clave pública codificada en Base64, sino solo de algunas partes (binarias) de la misma.

Para una clave OpenPGP versión 3, lo que tendrías que hacer es:

  1. Analizar el paquete de clave pública OpenPGP
  2. Para RSA, extraiga módulo y exponente
  3. Concatenar sus valores binarios
  4. Calcular el hashsum

Para los pasos 1 y 2, puede confiar en la herramienta pgpdump, que puede analizar y generar los números usando la -ibandera.

Para las claves de la versión 4, la cosa se vuelve aún más complicada.

Si desea calcular el hashsum con fines educativos, le recomiendo ampliarlo pgpdumpy utilizar todo el código del analizador disponible, para que pueda trabajar directamente en la información extraída. Estoy bastante seguro de que manejar la información binaria será más fácil que con el código shell puro.

ACTUALIZAR:Usaste los números enteros incorrectos. Utilice los que están en líneas RSA ny RSA een su lugar. Reuniendo todo en unas pocas líneas usando herramientas estándar:

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

pgpdump -iVuelca los MPI de la clave, que grepeliminamos cutde todo lo que no necesitamos, tranulamos todos los espacios en blanco, los convertimos a binario perly finalmente calculamos el archivo md5sum.

Ejecutando con la clave que proporcionó:

$ 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 más o menos lo que estamos buscando.

Para completar, lo mismo para las claves de la versión 4, donde necesita otra cadena de herramientas. Necesitamos el paquete de clave pública completo. Para descomponer un mensaje OpenPGP, gpgsplitresulta útil. Luego, puedes calcular inmediatamente el tamaño sha1sumdel archivo:

gpgsplit publickey.pub; sha1sum *.public_key

Por ejemplo, ejecutando con mi propia clave:

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

información relacionada