TDataDecrypter.py is a self-contained,dependency-light forensic/recovery tool for
Telegram Desktop / AyuGram tdata folders. It decrypts local storage, extracts and
validates every recoverable media file, reconstructs sliced videos, decodes cache
file_ids and usage timestamps, recovers account activity (searched peers, hashtags,
bots, video playback positions), and produces a clean text dump all offline, all in
one Python file.
Works with plain (no-passcode) and passcode-protected tdata folders, including the old
key_dataskey format and the newerkey_datassformat. Unknown passcodes can be exported as a$telegram$hash for John the Ripper / hashcat.
| Category | Details |
|---|---|
| LocalKey | Derived from key_datas/key_datass via CreateLocalKey (PBKDF2-HMAC-SHA512, 100 000 iterations in passcode mode, 1 iteration otherwise). Verified against infoEncrypted. |
| Passcode hash export | --john prints a $telegram$2*100000*... hash for offline cracking with John the Ripper (--format=telegram) or hashcat. |
| Account profile | Phone, username, service links (from decrypted settings + usertag). |
| Account activity | Top/recent peers from search suggestions (names, phones, usernames, ratings), sent/searched hashtags, recent bots, and media playback positions (the second a video was last stopped at). Output: activity/activity.txt + activity.json. |
| Photos | JPEG (structural validation + trailing-garbage trimming after last EOI), PNG, AVIF. Complete and partial (.jpg.partial) files. |
| Videos | MP4/MOV/WebM via full box-walk with 32/64-bit sizes; playable fragments kept as .frag.mp4. |
| Video reconstruction | media_cache slices (128 KB parts, 8 MB slots) reassembled by moov-heuristic: overlap-VERIFIED stitching, full-8MB-slot completion (ASSUMED), tail slots by size. Holes zero-filled — partial results stay playable. |
| Stickers | .tgs (first gzip member + Lottie-JSON structure check), WebP, WebM. |
| Voice | OGG/Opus with signature + page validation. |
| Files | GIF, PDF, MP3, WAV, SVG, SQLite and other sniffed formats. |
| file_id recovery | Cache keys are decoded into Telegram media IDs (the 19-digit int64 document_id / photo_id): files are named voice_7916765197716291584.ogg, photo_123_x.jpg, video_456.mp4. |
| Timestamps | StoreWithTime.system from the cache binlog restores each file's original caching time — applied as the file's mtime and recorded in media_index.jsonl. |
| Texts | Structured QDataStream (UTF-16BE) parsing + regex fallback (BE/LE/UTF-8), language scoring (EN/RU bigrams), CamelCase/hump analysis for sticker-pack names → clean texts/messages.txt + raw dump. |
| Drafts & settings | Drafts and self-serialized data from map files. |
| AyuGram extras | ayudata.db (SQLite: deleted/edited messages) dumped if present. |
| Caches | TDEF (lib_storage AES-CTR, 64-byte salt), binlog indexes (MultiStore/MultiRemove/MultiAccess with time tracking, XXH32 checksums), place-file layout (XX/xxxx buckets). |
Everything is deduplicated by SHA-256.
out/
├── photos/ # valid jpg/png/avif, named by file_id (image_1234567890123456789.jpg)
│ └── partial/ # truncated but viewable images
├── videos/ # mp4/mov/webm (full), named video_<document_id>.mp4
│ └── partial/ # playable fragments (.frag.mp4)
├── stickers/ # tgs / webp / webm
├── voice/ # ogg/opus, named voice_<document_id>.ogg
├── files/ # everything else that sniffed OK
├── texts/ # messages.txt (clean) + raw dump
├── activity/ # activity.txt + activity.json (peers, hashtags, bots,
│ # playback positions)
├── media_index.jsonl # per-file: file_id, key kind, dc, content tag, size,
│ # store_time, use counter, place
├── ayu/ # ayudata.db dump (AyuGram only)
└── report.txt # run summary
File mtimes are restored to the moment each media item was cached.
- Python 3.9+
pycryptodome(AES)tgcrypto— optional, pure-Python AES-IGE fallback is built in
pip install pycryptodome
# optional speed-up:
pip install tgcryptoNo GUI toolkit required for CLI mode. The GUI uses tkinter (bundled with Python on
Windows/macOS; on Linux install python3-tk).
python TDataDecrypter.pyPick the tdata directory, pick an output directory, press Start. If the key
fails without a passcode, the GUI will ask for one.
python TDataDecrypter.py <path_to_tdata> <output_dir> [--passcode 1234]Examples:
# no passcode
python TDataDecrypter.py "C:/tdata" ./out
# passcode-protected
python TDataDecrypter.py "C:/tdata" ./out --passcode 1234
# unknown passcode: export a hash for John the Ripper / hashcat
python TDataDecrypter.py "C:/tdata" --john > hash.txt
john --format=telegram hash.txtpython TDataDecrypter.py --selftestVerifies the crypto core against reference vectors (AES-IGE round-trips, XXH32 test vectors, passcode-mode KDF, TDF format parsing) plus the cache-key decoder, peer-id deserialization, activity parsers and the JtR hash format.
- Key:
key_datas/key_datass→ salt + encrypted key + encrypted info.CreateLocalKey=PBKDF2-HMAC-SHA512(sha512(salt+pass+salt), 1|100000 iters, 256B). Correctness is proven by decryptinginfoEncrypted. - TDF files (
TDF$magic): 16-byte SHA1 prefix + AES-IGE payload,u32 LElength embedded in the length field, MD5 trailer. - TDEF caches (lib_storage): 64-byte salt, AES-CTR with
key = sha256(k[:128]‖salt[:16]),iv = sha256(k[128:]‖salt[16:32])[:16], SHA-256 header checksum. - Places:
XX/xxxx…buckets derived by reverse-nibble hex of the file id; files decrypted with the cache key directly. - media_cache: 128 KB parts packed into 8 MB slices; header maps are either
contiguous or complex (absolute offsets). Binlog lost? Orphan slices are salvaged
and stitched by content overlap around the
moovbox. - Cache keys:
StorageCacheKey/bigFileBaseCacheKeylayouts are decoded into key kind + dc + media id (document_id,photo_id), giving every recovered file its Telegram-side 19-digit file_id;StoreWithTimerecords carry the caching time. - Validation: every output file passes a format-specific structural check (not just a magic-number sniff) before being placed into a category folder.
- Chat history is not stored in tdata (Telegram keeps it server-side) — expect drafts, names, and locally cached artifacts, not full conversations.
- file_id recovery and timestamps require a live
binlogin the cache databases; if the binlog was lost, files are still recovered but without ids/times. - Video reconstruction is heuristic: VERIFIED slices are byte-exact, ASSUMED ones are best-effort; unrecoverable holes are zero-filled.
ayudata.dbexists only if the tdata comes from AyuGram with its history module enabled.
- For non-commercial use only — see LICENSE (PolyForm Noncommercial 1.0.0).
- Use only on your own data or data you are explicitly authorized to process.
- The author take no responsibility for misuse.
PolyForm Noncommercial 1.0.0 — see LICENSE.