Summary

Since a Gnupg update, the tool will refuse to perform decryption when using only the private key. There is technically no reason for this and gpg does not provide any easy option to regenerate or calculate the public key from the private key or ways to ignore the missing key. As newer versions of gnupg will create secret key exports that also include the 013.user_id and 002.sig packets, importing will regenerate the public key structures automatically. If these are missing due to special circumstances, modification or the secret key export is just too old, this method is not available. Searching the net reveals that such unusable private key exports are causing pain for quite some users, e.g. when:

The gpg results and messages are different depending on attempted operation, e.g.

There are ideas for workarounds outlined on some forums, but gnupg mailing list responses are usually that it is not possible to fix it with the standard tools. A fully protocol aware solution will require parsing the Openpgp key structures and regenerating the public key structure and thus is quite complex. This article shows a VERY dirty way to regenerate 4096 bit public keys from private keys using peculiarities of the Openpgp storage format without parsing it or applying complex tools. Thus this procedure MAY FAIL in some cases depending on the private key data.

Details

This hack only requires the gnupg tools and standard Linux command line programs (sed, xxd). It uses the gpg1 Debian package as this version of gpg is more error tolerant, thus needing less fixes.

Core of the method is that the OpenPGP structure for the private keys as defined in RFC 4880, section 5.5.1 also includes the full public key structure as preamble, just the private key specific data is appended to it. Thus a private key packet can be turned into a public key packet just by

The following steps work for 4096 bit RSA keys, where type and packet length are always the same. It requires to change the tag types from 5 (secret key packet) to 6 (public key packet) and 7 (secret subkey packet) to 14 (public subkey packet). Assuming the secret key to be transformed is stored in secring.gpg, this can be done by following sequence of commands:

# gpg1 --homedir . --list-keys # gpg1 --homedir . --list-secret-keys sec 4096R/33CA869F 2019-09-12 ssb 4096R/205C792E 2019-09-12 # gpg1 --homedir . --export-options "export-reset-subkey-passwd" --export-secret-subkey 33CA869F | xxd -p | sed -r -e 's/950215/99020d/g' | xxd -r -p | gpgsplit -; pgpdump 000001-006.public_key gpg: about to export an unprotected subkey You need a passphrase to unlock the secret key for user: "[User ID not found]" gpgsplit: invalid CTB 07 Old: Public Key Packet(tag 6)(525 bytes) Ver 4 - new Public key creation time - Thu Sep 12 15:29:51 UTC 2019 Pub alg - RSA Encrypt or Sign(pub 1) RSA n(4096 bits) - ... RSA e(17 bits) - ... # mv 000001-006.public_key recover.gpg # rm 0000*; gpg1 --homedir . --export-options "export-reset-subkey-passwd" --export-secret-subkey 33CA869F | xxd -p | sed -r -e 's/9d0718/b9020d/g' | xxd -r -p | gpgsplit -; pgpdump 000002-014.public_subkey gpg: about to export an unprotected subkey You need a passphrase to unlock the secret key for user: "[User ID not found]" gpgsplit: invalid CTB 00 Old: Public Subkey Packet(tag 14)(525 bytes) Ver 4 - new Public key creation time - Thu Sep 12 15:29:51 UTC 2019 Pub alg - RSA Encrypt or Sign(pub 1) RSA n(4096 bits) - ... RSA e(17 bits) - ... # cat 000002-014.public_subkey >> recover.gpg # mv recover.gpg pubring.gpg # gpg1 --homedir . --list-keys ./pubring.gpg ------------- pub 4096R/33CA869F 2019-09-12 sub 4096R/205C792E 2019-09-12 # gpg1 --homedir . --decrypt encrypted.gpg > decrypted ...

The use of pgpdump is optional, it just displays some information on the key and hence allows to detect errors during the conversion process. Note that the --list-secret-keys command above did not print a uid-line between sec and ssb due to missing public key and specific secring frames, thus preventing gpg2 operation.

20191212 update: A mailing list user recommended to use Sequoia for that purpose. I did not test the method but following resources and procedures were pointed out:

If you only have the MPIs, then you can use something like the Key4::import_secret_rsa: https://docs.sequoia-pgp.org/sequoia_openpgp/packet/key/struct.Key4.html#method.import_secret_rsa You can find an example of how to do it here: https://docs.sequoia-pgp.org/src/sequoia_openpgp/packet/key/mod.rs.html#1484

References

Revisions

Comments

There is no forum in place yet. Send any comments to me-at-halfdog-net citing the page URL to add the comment, also referencing previous comments you want to reply to.

Last modified 20191212
Contact e-mail: me (%) halfdog.net