Search code examples
python-3.xencryptiongnupgopenpgp

How do I tell if OpenPGP encryption is symmetric or asymmetric?


Is there a way to tell if things encrypted via the GNU Privacy Guard are symmetric or asymmetric (without decrypting them or already knowing to start with)? How?

Anyway (for those who want to know what I'm doing), I used Python 3.x to program a GUI-based IDE of sorts that can open symmetrically encrypted files (and save them, too). It can open asymmetrically encrypted files (enter the passphrase to use your secret key instead of the passphrase to decrypt a symmetrically encrypted file). However, it doesn't know they're asymmetric and will overwrite them with symmetrically encrypted files if saved. It would be nice to be able to save them asymmetrically, too. My editor uses the gpg command-line program on Linux (no gpg libraries or anything like that).

I could have a checkbox on the password prompt for asymmetric encryption, but I'd rather not make it so it has to be a manual thing for the user.

For my own personal files, I could add some kind of marker to the saved files to distinguish, but I want it to be able to open them correctly even if they weren't created in my IDE.

I know there's a question with a similar title, but the question asked in the body is fundamentally different.


Solution

  • OpenPGP is a hybrid cryptosystem, which means messages (or files) are always encrypted symmetrically using a so-called session key. The session key again is encrypted using asymmetric encryption (using a public key) or symmetric encryption again (using a string to key function).

    This has technical reasons (asymmetric cryptography is very slow for large amounts of data), but also practical ones: by encrypting the small session key multiple times (once for each recipient), you can also have multiple recipients with different keys and even mix asymmetric (public key) and symmetric (password based) encryption in a single OpenPGP message.

    Each of those encrypted copies of the session key form an OpenPGP packet, either a packet with tag 1 (Public-Key Encrypted Session Key Packet) or a packet with tag 3 (Symmetric-Key Encrypted Session Key Packet). Those packets in an OpenPGP message can be easily decomposed using pgpdump. An example using GnuPG to create an OpenPGP message encrypting for both my own key and symmetrically for the passphrase foo:

    $ echo foo | gpg --recipient a4ff2279 --symmetric --passphrase foo --encrypt | pgpdump
    Old: Public-Key Encrypted Session Key Packet(tag 1)(524 bytes)
        New version(3)
        Key ID - 0xCC73B287A4388025
        Pub alg - RSA Encrypt or Sign(pub 1)
        RSA m^e mod n(4096 bits) - ...
            -> m = sym alg(1 byte) + checksum(2 bytes) + PKCS-1 block type 02
    Old: Symmetric-Key Encrypted Session Key Packet(tag 3)(46 bytes)
        New version(4)
        Sym alg - AES with 128-bit key(sym 7)
        Iterated and salted string-to-key(s2k 3):
            Hash alg - SHA512(hash 10)
            Salt - 0c a6 e6 1d d2 f4 9a 50 
            Count - 102400(coded count 105)
        Encrypted session key
            -> sym alg(1 bytes) + session key
    New: Symmetrically Encrypted and MDC Packet(tag 18)(63 bytes)
        Ver 1
        Encrypted data [sym alg is specified in sym-key encrypted session key]
            (plain text + MDC SHA1(20 bytes))
    

    Each of the first two packets forms a key to open the encrypted string in the Symmetrically Encrypted and MDC Packet.

    This also already explains how to analyze how a message was encrypted: look through the packets, looking for either tag 1 or 3 packets, indicating asymmetric or symmetric encryption (and be aware both might exist). You seem to be very lucky, and the Python GnuPG module already brings a ListPackets class, so you neither have to interface pgpdump nor write your own OpenPGP parser.