7

I'm trying to produce very small OpenPGP-encrypted files that can be embedded into QR codes.

However, in comparison to for example OpenSSL, GnuPG seems to produce very large results for a simple input of 'a':

$ echo -n a|openssl enc -aes-256-ctr|wc -c 
17
$ echo -n a|gpg --symmetric -o-|wc -c
71

From reading the manual, one of the differences is that OpenSSL by default only includes an 8 byte header and an 8 byte salt, while GnuPG also includes a salt, checksums and compression. With those turned off, the file size is lower but still high:

$ echo -n a|gpg --symmetric --compress-algo none --disable-mdc --s2k-mode 0 -o-|wc -c
35

Is there any way to optimize the OpenPGP-encrypted message even further (while keeping AES enabled)?

8

While the binary format of GnuPG is rather space-efficient, it is built for flexibility, not for absolutely minimal message sizes (usually, the actual message is much larger than a few bytes). The minimal "usual" OpenPGP message is 31 bytes large, but you can still cut down to 26 bytes with some additional effort, which is the smallest possible OpenPGP v4 message for single-byte content.

Dissecting an OpenPGP Message, Counting Bytes

By looking at RFC 4880, you can derive some minimal length of a message, which you cannot go below.

Lets have a look at the output of the command you constructed:

$ echo -n a|gpg --symmetric --compress-algo none --disable-mdc --s2k-mode 0 -o-|gpg --list-packets
gpg: Note: simple S2K mode (0) is strongly discouraged
    gpg: AES encrypted data
gpg: encrypted with 1 passphrase
gpg: WARNING: message was not integrity protected

The first packet is a symmetric-key encrypted session key packet. It holds a copy of the session key, encrypted by the passphrase using the string-to-key mechanics. OpenSSL does not do this, but you cannot skip this unless providing the session key instead of a passphrase, providing the session key separately (discussed below). This packet is six bytes large and built from:

# off=0 ctb=8c tag=3 hlen=2 plen=4
:symkey enc packet: version 4, cipher 7, s2k 0, hash 10

Now, the encrypted data packets starts. It contains:

  • 2 bytes packet header (tag and length)
  • 18 bytes random prefix (instead of the 0-byte IV, OpenPGP CFB uses a random prefix in the size of the cipher block, and repeats the first two bytes; AES uses 128 bits = 16 bytes as cipher block length)
# off=6 ctb=c9 tag=9 hlen=2 plen=26 new-ctb
:encrypted data packet:
    length: 26

OpenPGP always stores the message in a literal data packet, which adds some meta data. Disabling compression at least removes the additional compression headers. This packet finally adds another 9 bytes:

  • 2 bytes packet header (tag and length)
  • 1 byte data format
  • 1 byte file name string length (value zero, no filename following)
  • 4 bytes timestamp
  • 1 byte content
# off=26 ctb=cb tag=11 hlen=2 plen=6 new-ctb
:literal data packet:
    mode b (62), created 1503680075, name="",
    raw data: 0 bytes

To wrap up: you will not be able to save a single further byte -- unless you omit the string-to-key derivation and directly use the session key instead of a passphrase.

Omitting the String-to-Key Function

GnuPG allows to read and set the session key using --show-session-key and --override-session-key. Reading the message composition chapter, I was actually surprised that valid OpenPGP messages do not require any packet defining the encryption of the session key at all. GnuPG indeed supports this kind of operation, but I would not bet for other implementations as this is a very esoteric way of using OpenPGP.

   OpenPGP Message :- Encrypted Message | Signed Message |
                      Compressed Message | Literal Message.
   Encrypted Message :- Encrypted Data | ESK Sequence, Encrypted Data.
   Encrypted Data :- Symmetrically Encrypted Data Packet |
         Symmetrically Encrypted Integrity Protected Data Packet

Doing so should save the 6 bytes of the symmetric-key encrypted session key packet.

Constructing an OpenPGP Message Without Symmetric-Key Encrypted Session Key Packet

I did not find a way to make GnuPG use a predefined session key. But you can generate a symmetrically encrypted message, extract the session key during decryption and then split apart the message.

Encrypting the message:

$ echo -n a|gpg --symmetric --compress-algo none --disable-mdc --s2k-mode 0 -o message.gpg

Extracting the session key (will ask for the passphrase):

$ gpg --show-session-key 0 --decrypt message.gpg
gpg: AES encrypted data
gpg: encrypted with 1 passphrase
gpg: session key: '7:F7FBBA6E0636F890E56FBBF3283E524C'
agpg: WARNING: message was not integrity protected

Split apart the OpenPGP message into individual packets:

$ gpgsplit message.gpg

The folder now holds four files: the encrypted message.gpg, the unencrypted message, the symmetric-key encrypted session key packet 000001-003.sym_enc and finally the encrypted data packet 000002-009.encrypted.

$ ls -l
total 16
-rw-r--r-- 1 jenserat jenserat  6 Aug 25 19:36 000001-003.sym_enc
-rw-r--r-- 1 jenserat jenserat 29 Aug 25 19:36 000002-009.encrypted
-rw-r--r-- 1 jenserat jenserat  1 Aug 25 19:04 message
-rw-r--r-- 1 jenserat jenserat 35 Aug 25 19:33 message.gpg

You could also concatenate the individual packet files to get back message.gpg -- those two files are just split apart parts of message.gpg. Observe the file sizes, which exactly matches the sizes discussed above (while of of course the size of the literal data packet is contained in the encrypted data packet, since gpgsplit is not aware of the passphrase)!

Decrypting a Separate Encrypted Data Packet

This step is rather simple:

$ gpg --override-session-key '7:F7FBBA6E0636F890E56FBBF3283E524C' --decrypt 000002-009.encrypted 
agpg: WARNING: message was not integrity protected

Don't overlook the a in front of the warning message, which is the output.

Meaning of the Warning Messages

GnuPG printed two warning messages.

gpg: Note: simple S2K mode (0) is strongly discouraged

This is because the simple S2K mode makes brute force and dictionary attacks on the passphrase cheap and easy as it uses no hashing and no salt.

Latter of course enables using the same session key for multiple files encrypted using the same passphrase, but be aware of the consequences.

gpg: WARNING: message was not integrity protected

This warning message tells that the message might have been changed by an attacker without the decrypting party being able to be aware of this fact. This is because of --disable-mdc -- which of course saves some bytes for the encrypted checksum of the file. You can try on your own by modifying the last byte in a hex editor.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.