What Is Messaging Forensics? A Practitioner's Primer
Messaging forensics explained: acquisition options, a normalized message model, the timestamp zoo, evidence hashing and the pitfalls that break timelines.
Messaging forensics is the collection, parsing, normalization and interpretation of communications, from email and SMS to chat apps and workplace collaboration tools, so that they can answer questions of fact: who said what to whom, when, over which channel, and what was shared, edited or removed along the way.
It sits at the crossroads of mobile forensics, computer forensics, eDiscovery and incident response. A business email compromise case needs mail headers. An insider case needs Slack or Teams. A fraud case needs WhatsApp, Telegram and SMS, often all three for the same people. The hard part is rarely reading a single message. It is combining hundreds of thousands of them, from formats that disagree on almost everything, into one timeline you can defend.
This primer covers the fundamentals that apply to every source: how evidence is acquired, what a normalized message looks like, how timestamps are encoded, how to preserve integrity, how to link identities, and which traps catch experienced examiners.
Acquisition: three routes, three trade-offs
Where the data comes from dictates what it contains. There are broadly three routes.
App exports
Most platforms offer a user-facing export: WhatsApp "Export chat", Telegram Desktop's JSON export, Slack workspace exports, the Discord data package, Meta's "Download your information", Google Takeout, Skype's conversation export, LINE's chat history export.
- Pros: easy to obtain, often with the owner's cooperation; no device access or root required; structured (JSON, CSV) or at least predictable text.
- Cons: the vendor decides what is included. Exports tend to drop deleted messages, edit history, read receipts and internal identifiers. Some are text-only with device-local times and no time zone. Many are scoped to what one account can see.
Device and database extraction
The alternative is to pull the application's own store from a device, a backup or a desktop profile: chat.db on macOS, sms.db from an iOS backup, msgstore.db and wa.db on Android, ChatStorage.sqlite on iOS, Signal Desktop's db.sqlite, Viber's viber.db, Skype's legacy main.db, mmssms.db for Android SMS.
- Pros: richer data. You get internal row IDs, delivery and read state, edit timestamps, attachment metadata, and sometimes remnants of deleted records in free pages or write-ahead logs (
-walfiles). - Cons: access is the problem. Mobile stores sit behind the OS sandbox and often behind encryption: Signal Desktop uses SQLCipher, WhatsApp Android backups are encrypted, and Telegram Desktop's
tdatais encrypted at rest. Schemas also change between app versions without notice.
Provider, legal process and eDiscovery
The third route goes through the service operator: a subpoena or court order to the provider, or an administrative export from the tenant you control, such as Microsoft Purview eDiscovery for Teams and Exchange, a Slack Enterprise Grid export, or Google Vault.
- Pros: complete coverage of the tenant, server-side retention (including items users deleted locally, if retention policies apply), and a clear chain of custody from an authoritative source.
- Cons: only available to the right party (employer, law enforcement, litigant), slow, and delivered in the provider's format, which may flatten threads or render chats as email-like items. Teams eDiscovery exports, for example, represent chat messages as
.msgitems of classIPM.SkypeTeams.Message.
In practice you combine routes. A user export fills gaps a device extraction cannot reach, and an eDiscovery export corroborates what was found on a laptop.
A normalized message model
To put an email, a Slack post and a WhatsApp line on the same timeline, map every source onto one schema. The MessagingForensics tool uses the following fields per message; they make a good baseline for any toolkit:
| Field | Meaning | Notes |
|---|---|---|
timestamp | When the message was sent | ISO 8601 in UTC, or null when the source has no usable time |
platform | Email, Slack, WhatsApp, Teams… | Fixed vocabulary, not free text |
conversation | Channel, chat, thread or mailbox folder | Group names change; keep IDs in extra |
sender / senderId | Display name and stable identifier | Names are cosmetic, IDs are evidence |
recipients | Addressees, when the format records them | Email always, most chat exports never |
text / subject | Body as plain text | HTML reduced to text; subject for email |
attachments | Name, MIME type, size | Content is usually a separate file |
edited | Time of last edit | Only when the source records it |
deleted | Tombstone flag | "This message was deleted" is data too |
replyTo | Parent message reference | In-Reply-To for email, thread or quote IDs for chat |
direction | in or out relative to the owner | Only meaningful for device stores |
format / source | Parser used and evidence path | E.g. archive.zip!/general/2024-02-01.json |
extra | Everything source-specific | Raw headers, IDs, flags kept for the record |
Two design rules matter. First, never throw away source-specific data just because it does not fit a column; park it in an extra map so a reviewer can go back to it. Second, every row must point back to the exact evidence file and parser that produced it. A timeline entry you cannot trace to its origin is an assertion, not evidence.
The timestamp zoo
Nothing breaks messaging timelines like time. Each platform picks its own epoch, unit and time zone convention, and sometimes several within one database. The table uses the same instant, 2024-02-01 10:15:00 UTC, in each encoding.
| Encoding | Epoch and unit | Example value | Where you meet it |
|---|---|---|---|
| Unix seconds | 1970-01-01 UTC, seconds | 1706782500 | Slack ts (with a microsecond suffix), Telegram date_unixtime, Skype legacy main.db |
| Unix milliseconds | 1970-01-01 UTC, ms | 1706782500000 | Android SMS date, WhatsApp Android, Signal Desktop sent_at, Messenger timestamp_ms |
| Apple Cocoa seconds | 2001-01-01 UTC, seconds | 728475300 | WhatsApp iOS ZMESSAGEDATE, older iMessage chat.db |
| Apple Cocoa nanoseconds | 2001-01-01 UTC, ns | 728475300000000000 | iMessage chat.db / sms.db on current macOS and iOS |
| Windows FILETIME | 1601-01-01 UTC, 100-ns ticks | 133512561000000000 | Outlook .msg properties, Teams eDiscovery items |
| RFC 2822 date | Text with explicit offset | Thu, 01 Feb 2024 11:15:00 +0100 | Email Date: and Received: headers |
| ISO 8601 | Text, offset optional | 2024-02-01T10:15:00.000+00:00 | Discord data packages, Microsoft Graph, Teams JSON |
| Device-local text | No zone recorded | 01/02/2024, 11:15 | WhatsApp .txt export, LINE .txt export |
The conversions are simple arithmetic once you know the epoch:
unix_seconds = cocoa_seconds + 978307200
unix_seconds = cocoa_nanoseconds / 1e9 + 978307200
unix_seconds = filetime / 1e7 - 11644473600
The tool applies those rules and auto-scales Unix values: anything above 1e13 is divided down from micro- or nanoseconds, and anything below 1e11 is treated as seconds. Values that land before 1990 or after 2100 are rejected as implausible and the timestamp is left empty rather than invented. That guard matters: a zero, a column mix-up or a Cocoa value read as Unix produces dates in 1970 or 2001 that look real on a chart.
Text formats deserve extra suspicion. WhatsApp and LINE text exports print the phone's local clock with no zone, and the date order (day/month or month/day) depends on the device locale. A value like 03/04/2024 is ambiguous until you see a day above 12 elsewhere in the file. The tool infers the order from the whole file, shows the local time as-is labelled UTC, and emits a warning so nobody mistakes it for true UTC. Likewise, zone-less ISO-style strings such as 2024-01-02 10:00:00 are read as UTC, which is what most exports mean but not all. Record your assumption and the device's time zone setting in your notes.
Email is the opposite problem: every header carries an explicit offset, but the Date: header is written by the sender's client and can be set to anything. The Received: chain, stamped by each relay, is the independent clock to compare against.
Evidence integrity
Messaging evidence is small, text-heavy and trivially editable, which is why integrity practice has to be boring and consistent.
- Hash before you parse. Compute a cryptographic hash (SHA-256 is the modern default) of every file as received, before any tool opens it. MessagingForensics does this first: each input file is hashed in the browser before archives are expanded or anything is parsed, and the hash appears in the evidence table and reports.
- Work on copies. SQLite in particular will happily modify a file when opened read-write: it can replay and checkpoint a
-waljournal into the main database, destroying the pre-checkpoint state. Keep the original, the-waland the-shmtogether, and parse copies. - Record the parser and format per file. "Parsed as Slack workspace export" or "Parsed as WhatsApp iOS (ChatStorage.sqlite)" belongs next to the hash. If a file was not recognized, say so explicitly; silent omissions are how gaps go unnoticed.
- Keep archive paths. Messages inside a ZIP should cite the member path (
export.zip!/Messages/c123/messages.json), not just the archive, so a reviewer can find the exact bytes. - Process locally when you can. Uploading evidence to a third-party service creates a new copy you do not control. An in-browser parser keeps the data on the examiner's machine.
Correlating identities across platforms
The same person shows up as +1 202 555 0143 in SMS, 12025550143@s.whatsapp.net in WhatsApp, jdoe in Slack, a numeric snowflake in Discord and j.doe@example.test in email. Display names collide and change; identifiers are what you pivot on.
Useful anchors:
- Phone numbers normalized to E.164 (
+12025550143). WhatsApp JIDs, Signal, Viber, iMessage handles and SMS addresses all reduce to them. - Email addresses, lower-cased. They link email, iMessage (Apple ID handles), Teams and Slack profiles, and Google Chat.
- Platform user IDs from
extrafields: SlackU…IDs, Discord snowflakes, Telegramuser…IDs. These survive renames. - Shared indicators in content: the same URL, crypto wallet or IBAN appearing across platforms is often the strongest link between accounts.
MessagingForensics extracts URLs, email addresses, phone numbers in international format, IPv4 addresses, Bitcoin and Ethereum addresses and IBANs from message text and subjects, and counts how many messages contain each. The patterns are deliberately conservative: a phone number must start with +, so local-format numbers are not captured. Treat the list as a starting point for pivots, not an exhaustive inventory. Watchlists (keywords one per line) then filter the combined timeline to the messages that matter.
Common pitfalls
Encrypted stores. Signal Desktop's database is SQLCipher-encrypted; you need the key from the profile to decrypt it before any parser can read it. WhatsApp Android backups (.crypt14, .crypt15) need the key file or the end-to-end backup password. Telegram's desktop cache is encrypted. If you only have the encrypted file, the answer is acquisition, not parsing.
Owner-only exports. Discord's data package contains only the messages the requesting account sent, not the other side of the conversation. Messenger and Instagram exports reflect what that account could see at export time. Always state whose perspective an export represents.
Deleted messages. Exports rarely include them; databases sometimes do, as tombstone rows or in unallocated pages. A "deleted" flag in the normalized model means the source recorded a deletion. The absence of a message proves nothing about whether it existed.
Time zones. Mixing device-local text exports with UTC database timestamps can shift events by hours and reorder a conversation. Normalize everything to UTC, flag the rows that could not be normalized, and note daylight saving transitions in the period.
Duplicates across sources. The same message may appear in a WhatsApp export, in msgstore.db and in the counterpart's phone, or the same email in two mailboxes and an MBOX. Deduplicate on stable identifiers (Message-ID, platform message IDs) where they exist, and on timestamp plus sender plus text hash otherwise, but keep every source reference: a message found on both devices is stronger evidence than one found on either.
Edits. An edited message may carry only its final text. If the source records an edit time, keep it; if the platform keeps history, it lives in another table or file.
Display-name drift. Contacts are renamed, groups are retitled, and exports print whatever name was current at export time, not at send time.
Next steps
Try the MessagingForensics parser: drop exports or databases into the browser, get SHA-256 hashes for every file, a unified UTC timeline, extracted indicators and CSV, JSON or HTML reports, without uploading anything.
Then go deeper by source: