Hi,
in the past I found out that the AEAD EAX releases unauthenticated plaintext [1]. Since then, GnuPG was modified to use OCB. I have done a basic bit-flipping test on OCB, and while gcry_cipher_checktag reports a checksum error, it does release unauthenticated plaintext, and also reports a DECRYPTION_OK/GOODMDC status line.
[1] https://mailarchive.ietf.org/arch/msg/openpgp/tN2jWx4hUtiMGSo8-ILRXYNumVY/
I am including a docker file to build GnuPG and generate a test key, as well as a test script to illustrate the problem. When I run this, I get:
# python3 test-aead.py gpg: NOTE: THIS IS A DEVELOPMENT VERSION! gpg: It is only intended for test purposes and should NOT be gpg: used in a production environment or with production keys! Bit flipped at byte 109863 bit 4 gpg: NOTE: THIS IS A DEVELOPMENT VERSION! gpg: It is only intended for test purposes and should NOT be gpg: used in a production environment or with production keys! [GNUPG:] ENC_TO C449D89F69F97CF9 18 0 [GNUPG:] KEY_CONSIDERED F837EEC1E3043DEA0C6BDA1B5F6F2B595BFD6E2B 0 gpg: encrypted with cv25519 key, ID C449D89F69F97CF9, created 2024-03-13 "aeadtest" [GNUPG:] KEY_CONSIDERED F837EEC1E3043DEA0C6BDA1B5F6F2B595BFD6E2B 0 [GNUPG:] KEY_CONSIDERED F837EEC1E3043DEA0C6BDA1B5F6F2B595BFD6E2B 0 [GNUPG:] DECRYPTION_KEY A1E7C406ED9548E816174D68C449D89F69F97CF9 F837EEC1E3043DEA0C6BDA1B5F6F2B595BFD6E2B u [GNUPG:] BEGIN_DECRYPTION [GNUPG:] DECRYPTION_INFO 0 9 2 [GNUPG:] PLAINTEXT 62 1710341062 declaration.txt [GNUPG:] PLAINTEXT_LENGTH 273600 gpg: gcry_cipher_checktag failed: Checksum error [GNUPG:] DECRYPTION_OKAY [GNUPG:] GOODMDC [GNUPG:] END_DECRYPTION File differs from byte 109702 to byte 109717 File 1: n and conscience File 2: \xb7\xdc\xd6\xd1\xd5\xc9\xf2\xfa_\x8b!\xc4T\x94 File differs from byte 272950 to byte 273599 File 1: equal in dignity and rights. They are endowed with reason and conscience and should act towards one another in a spirit of brotherhood. All human beings are born free and equal in dignity and rights. They are endowed with reason and conscience and should act towards one another in a spirit of brotherhood. All human beings are born free and equal in dignity and rights. They are endowed with reason and conscience and should act towards one another in a spirit of brotherhood. All human beings are born free and equal in dignity and rights. They are endowed with reason and conscience and should act towards one another in a spirit of brotherhood. File 2: \x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00
The source code for the test program:
import random import sys import os # Write the first line of the declaration of human rights a lot of times to a file. def write_file(filename): with open(filename, 'w') as f: for i in range(1600): f.write('All human beings are born free and equal in dignity and rights. They are endowed with reason and conscience and should act towards one another in a spirit of brotherhood.\n') def encrypt_file(filename, recipient): os.system('gpg --compress-algo=0 --encrypt --recipient ' + recipient + ' ' + filename) return filename + '.gpg' # Read a file, flip a random bit in it, and write the result back to a file. def flip_bit(filename): with open(filename, 'rb') as f: data = bytearray(f.read()) bit = random.randint(0, 8 * len(data) - 1) byte, bit = divmod(bit, 8) data[byte] ^= 1 << bit with open(filename, 'wb') as f: f.write(data) print("Bit flipped at byte", byte, "bit", bit) def decrypt_file(filename): os.system('gpg --status-fd=2 --output ' + filename[:-4] + ".out" + ' --decrypt ' + filename) return filename[:-4] + ".out" def printable_bytes(data): return data.replace(b'\x00', b'\\x00').decode('utf-8', 'backslashreplace') def compare_files(file1, file2): with open(file1, 'rb') as f1, open(file2, 'rb') as f2: data1 = f1.read() data2 = f2.read() differing_bytes = [] for i in range(len(data1)): if data1[i] != data2[i]: differing_bytes.append(i) # Summarise differing ranges. i = 0 while i < len(differing_bytes): start = differing_bytes[i] while i < len(differing_bytes) - 1 and differing_bytes[i] + 1 == differing_bytes[i + 1]: i += 1 end = differing_bytes[i] if start == end: print("File differs at byte", start) # Print the differing bytes in a human-readable format, using \xXX if necessary. Special care needs to be taken to represent zero bytes else: print("File differs from byte", start, "to byte", end) print("File 1:", printable_bytes(data1[start:end + 1])) print("File 2:", printable_bytes(data2[start:end + 1])) i += 1 if not differing_bytes: print("Files are the same") def main(): filename = 'declaration.txt' recipient = 'aeadtest' write_file(filename) encrypted_file = encrypt_file(filename, recipient) flip_bit(encrypted_file) decrypted_file = decrypt_file(encrypted_file) compare_files(filename, decrypted_file) main()
The docker file:
FROM ubuntu RUN apt-get update && apt-get install -y build-essential git wget \ libldap2-dev zlib1g-dev libbz2-dev libsqlite3-dev libreadline-dev \ pcscd file ca-certificates bzip2 texinfo bison xfig imagemagick \ autogen autoconf gettext pinentry-tty && apt-get clean RUN git clone git://git.gnupg.org/gnupg.git RUN git clone git://git.gnupg.org/libgpg-error.git RUN git clone git://git.gnupg.org/libgcrypt.git RUN git clone git://git.gnupg.org/libksba.git RUN git clone git://git.gnupg.org/npth.git RUN git clone git://git.gnupg.org/gpgme.git RUN git clone git://git.gnupg.org/libassuan.git RUN git clone git://git.gnupg.org/ntbtls.git # https://dev.gnupg.org/T4280 RUN rm /etc/ImageMagick-6/policy.xml # Optionally pass some flags to gnupg configure by adding: speedo_pkg_gnupg_configure= RUN (cd gnupg; ./autogen.sh && make -f build-aux/speedo.mk git-native gitrep=/) # This would be for release build. #RUN wget -c https://gnupg.org/ftp/gcrypt/gnupg/gnupg-2.2.11.tar.bz2 && tar xjf gnupg-2.2.11.tar.bz2 #RUN (cd gnupg-2.2.11; ./autogen.sh && make -f build-aux/speedo.mk native gitrep=git://git.gnupg.org) ENV PATH="/gnupg/PLAY/inst/bin:${PATH}" ENV LD_LIBRARY_PATH="/gnupg/PLAY/inst/lib" # Example for AEAD RUN gpg --batch --passphrase "" --quick-generate-key aeadtest RUN echo hallo | gpg -r aeadtest -eav | gpg -d