How to use GnuPG to get the smallest possible output (symmetric encryption)?
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:
- 2 bytes packet header (tag and length)
- 1 byte version number
- 1 byte symmetric algorithm ID
- 2 byte s2k specifier (1 byte "simple s2k mode", 1 byte hash function algorithm)
# 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 concat
enate 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.