From 9cedcf176717cf53c5ac1e19f6afb4001fc6f1bc Mon Sep 17 00:00:00 2001
From: KnugiHK <24708955+KnugiHK@users.noreply.github.com>
Date: Tue, 6 Jan 2026 23:00:36 +0800
Subject: [PATCH 01/52] Create conftest to oves test_nuitka_binary.py to the
end of testing
Moves test_nuitka_binary.py to the end and fails if the file is missing.
---
tests/conftest.py | 27 +++++++++++++++++++++++++++
1 file changed, 27 insertions(+)
create mode 100644 tests/conftest.py
diff --git a/tests/conftest.py b/tests/conftest.py
new file mode 100644
index 0000000..50f0866
--- /dev/null
+++ b/tests/conftest.py
@@ -0,0 +1,27 @@
+import pytest
+import os
+
+def pytest_collection_modifyitems(config, items):
+ """
+ Moves test_nuitka_binary.py to the end and fails if the file is missing.
+ """
+ target_file = "test_nuitka_binary.py"
+
+ # Sanity Check: Ensure the file actually exists in the tests directory
+ test_dir = os.path.join(config.rootdir, "tests")
+ file_path = os.path.join(test_dir, target_file)
+
+ if not os.path.exists(file_path):
+ pytest.exit(f"\n[FATAL] Required test file '{target_file}' not found in {test_dir}. "
+ f"Order enforcement failed!", returncode=1)
+
+ nuitka_tests = []
+ remaining_tests = []
+
+ for item in items:
+ if target_file in item.nodeid:
+ nuitka_tests.append(item)
+ else:
+ remaining_tests.append(item)
+
+ items[:] = remaining_tests + nuitka_tests
\ No newline at end of file
From 647e406ac0591b86ca484bd7ad0f79cc97cd3875 Mon Sep 17 00:00:00 2001
From: KnugiHK <24708955+KnugiHK@users.noreply.github.com>
Date: Thu, 8 Jan 2026 23:57:02 +0800
Subject: [PATCH 02/52] Implement early key validation via authenticated
decryption (#190)
Utilize `decrypt_and_verify` to immediately identify incorrect user-provided keys via GCM tag validation.
---
Whatsapp_Chat_Exporter/android_crypt.py | 14 +++++++++++++-
1 file changed, 13 insertions(+), 1 deletion(-)
diff --git a/Whatsapp_Chat_Exporter/android_crypt.py b/Whatsapp_Chat_Exporter/android_crypt.py
index 3e921d1..ed84041 100644
--- a/Whatsapp_Chat_Exporter/android_crypt.py
+++ b/Whatsapp_Chat_Exporter/android_crypt.py
@@ -112,8 +112,20 @@ def _decrypt_database(db_ciphertext: bytes, main_key: bytes, iv: bytes) -> bytes
zlib.error: If decompression fails.
ValueError: if the plaintext is not a SQLite database.
"""
+ FOOTER_SIZE = 32
+ if len(db_ciphertext) <= FOOTER_SIZE:
+ raise ValueError("Input data too short to contain a valid GCM tag.")
+
+ actual_ciphertext = db_ciphertext[:-FOOTER_SIZE]
+ tag = db_ciphertext[-FOOTER_SIZE: -FOOTER_SIZE + 16]
+
cipher = AES.new(main_key, AES.MODE_GCM, iv)
- db_compressed = cipher.decrypt(db_ciphertext)
+ try:
+ db_compressed = cipher.decrypt_and_verify(actual_ciphertext, tag)
+ except ValueError:
+ # This could be key, IV, or tag is wrong, but likely the key is wrong.
+ raise ValueError("Decryption/Authentication failed. Ensure you are using the correct key.")
+
db = zlib.decompress(db_compressed)
if db[0:6].upper() != b"SQLITE":
raise ValueError(
From 0ba81e0863a67f9a509e6a8cc00405df8d242ea2 Mon Sep 17 00:00:00 2001
From: KnugiHK <24708955+KnugiHK@users.noreply.github.com>
Date: Thu, 8 Jan 2026 23:59:31 +0800
Subject: [PATCH 03/52] Implement granular error handling
Added and improved layered Zlib and SQLite header checks to distinguish between authentication failures (wrong key) and data corruption.
---
Whatsapp_Chat_Exporter/android_crypt.py | 17 ++++++++++++++---
1 file changed, 14 insertions(+), 3 deletions(-)
diff --git a/Whatsapp_Chat_Exporter/android_crypt.py b/Whatsapp_Chat_Exporter/android_crypt.py
index ed84041..b8ed13a 100644
--- a/Whatsapp_Chat_Exporter/android_crypt.py
+++ b/Whatsapp_Chat_Exporter/android_crypt.py
@@ -126,11 +126,22 @@ def _decrypt_database(db_ciphertext: bytes, main_key: bytes, iv: bytes) -> bytes
# This could be key, IV, or tag is wrong, but likely the key is wrong.
raise ValueError("Decryption/Authentication failed. Ensure you are using the correct key.")
- db = zlib.decompress(db_compressed)
- if db[0:6].upper() != b"SQLITE":
+ if len(db_compressed) < 2 or db_compressed[0] != 0x78:
+ logger.debug(f"Data passes GCM but is not Zlib. Header: {db_compressed[:2].hex()}")
raise ValueError(
- "The plaintext is not a SQLite database. Ensure you are using the correct key."
+ "Key is correct, but decrypted data is not a valid compressed stream. "
+ "Is this even a valid WhatsApp database backup?"
)
+
+ try:
+ db = zlib.decompress(db_compressed)
+ except zlib.error as e:
+ raise zlib.error(f"Decompression failed (The backup file likely corrupted at source): {e}")
+
+ if not db.startswith(b"SQLite"):
+ raise ValueError(
+ "Data is valid and decompressed, but it is not a SQLite database. "
+ "Is this even a valid WhatsApp database backup?")
return db
From 75fcf33fda2b3a7f14fff919714327e090d52301 Mon Sep 17 00:00:00 2001
From: Cosmo
{{ msg.time }}
+ {% if msg.reactions %} +