Forensik Digital Kasus Hoax Sekolah #3
Bagian ketiga: dekripsi backup crypt15, bedah skema msgstore.db pada Android dan ChatStorage.sqlite pada iOS, konversi timestamp yang berbeda antar-platform, dan satu kolom yang paling menentukan dalam kasus hoax — forward_score.
Seri Forensik Digital Kasus Hoax Sekolah #1 Kerangka kerja & legalitas · #2 Akuisisi bukti · #3 Database WhatsApp (Anda di sini) · #4 Metadata media · #5 Timeline & laporan
Kita sudah punya database WhatsApp dari beberapa perangkat. Sekarang bagian yang sebenarnya menjawab pertanyaan investigasi.
Struktur database WhatsApp berbeda cukup jauh antara Android dan iOS, termasuk cara menyimpan waktu. Kalau Anda mencampur keduanya tanpa normalisasi, timeline yang dihasilkan akan salah — dan kesalahan itu tidak akan terlihat sampai seseorang menanyakannya di forum yang salah.
Prasyarat
- Backup WhatsApp dari bagian kedua sudah ada di
02-working/ - Kunci enkripsi 64 digit untuk file
.crypt15Android sqlite3dan DB Browser for SQLite terpasang- Lingkungan Python dari bagian pertama aktif
Sebelum mulai: pastikan Anda ikut menyalin file
-waldan-shmbila ada. Write-Ahead Log SQLite bisa memuat transaksi yang belum di-checkpoint ke database utama — termasuk pesan yang baru saja dihapus. Membuka database tanpa file-file ini bisa menghilangkannya.
1. Dekripsi backup Android
File .crypt15 memakai enkripsi end-to-end. Dengan kunci 64 digit dari pemiliknya, wa-crypt-tools bisa membukanya:
source ~/forensics-env/bin/activate
pip install wa-crypt-tools
cd $CASE/02-working/EV-002-android-S04/whatsapp-databases
# Simpan kunci ke file, jangan ketik di command line
# (command line masuk ke shell history)
wadecrypt ../../../00-admin/S04-backup.key \
msgstore.db.crypt15 \
msgstore-S04.db
Ulangi untuk backup harian — ini yang sering menyelamatkan kasus:
for f in msgstore-2026-08-*.crypt15; do
wadecrypt ../../../00-admin/S04-backup.key "$f" "decrypted-${f%.crypt15}.db"
done
Verifikasi hasilnya database SQLite yang valid:
sqlite3 msgstore-S04.db "PRAGMA integrity_check;"
sqlite3 msgstore-S04.db ".tables"
Kalau dekripsi gagal tanpa pesan jelas, 90% penyebabnya salah ketik pada kunci. Kunci itu 64 karakter heksadesimal — salin dari foto layar, jangan ketik ulang.
2. Peta skema Android
Sejak sekitar 2021 WhatsApp Android memakai skema ternormalisasi. Tabel yang penting untuk kita:
| Tabel | Isi |
|---|---|
message |
Satu baris per pesan: waktu, tipe, teks, referensi pengirim |
chat |
Satu baris per percakapan, termasuk subject untuk grup |
jid |
Identitas: nomor telepon dan ID grup |
message_media |
Metadata media, termasuk hash file |
message_forwarded |
Skor penerusan — kolom kunci kasus hoax |
message_quoted |
Isi pesan yang dikutip saat membalas |
receipt_user |
Waktu terkirim dan dibaca per anggota grup |
Kolom message yang sering dipakai:
SELECT sql FROM sqlite_master WHERE name = 'message';
_id— kunci primerchat_row_id— referensi kechat._idfrom_me— 1 kalau dikirim perangkat inikey_id— ID pesan global WhatsApp, sama di semua perangkat penerimasender_jid_row_id— referensi kejid._id, terisi untuk pesan gruptimestamp— waktu kirim, milidetik sejak Unix epochreceived_timestamp— waktu pesan diterima perangkat ini, milidetikmessage_type— kode tipe kontentext_data— isi teks
key_id layak digarisbawahi. Karena nilainya identik di seluruh perangkat, ia menjadi kunci penghubung saat Anda menggabungkan database dari lima ponsel berbeda menjadi satu timeline.
3. Konversi waktu
Android menyimpan milidetik sejak Unix epoch. Untuk WIB (UTC+7):
SELECT datetime(timestamp/1000, 'unixepoch', '+7 hours') AS waktu_wib
FROM message LIMIT 5;
Jangan pakai
'localtime'. Modifier itu memakai zona waktu laptop pemeriksa, yang bisa berbeda dan tidak akan tercatat di laporan. Selalu tulis offset eksplisit, dan selalu sebutkan zona waktu di setiap tabel hasil.
Selisih antara timestamp dan received_timestamp juga bermakna. Untuk pesan yang diterima, timestamp mendekati waktu pengiriman, sedangkan received_timestamp adalah waktu perangkat ini benar-benar menerimanya. Jeda besar berarti perangkat sedang offline — konteks penting saat menyusun urutan.
4. Temukan grup dan buat timeline
Daftar semua grup beserta jumlah pesannya:
SELECT c._id AS chat_id,
c.subject AS nama_grup,
j.raw_string AS group_jid,
COUNT(m._id) AS jml_pesan
FROM chat c
JOIN jid j ON j._id = c.jid_row_id
LEFT JOIN message m ON m.chat_row_id = c._id
WHERE j.server = 'g.us'
GROUP BY c._id
ORDER BY jml_pesan DESC;
Server g.us menandai grup; s.whatsapp.net menandai chat personal.
Sekarang timeline lengkap satu grup:
SELECT m._id,
datetime(m.timestamp/1000, 'unixepoch', '+7 hours') AS waktu_wib,
CASE m.from_me WHEN 1 THEN 'PEMILIK PERANGKAT'
ELSE COALESCE(sj.user, '(sistem)') END AS pengirim,
m.message_type,
COALESCE(mf.forward_score, 0) AS skor_teruskan,
substr(m.text_data, 1, 120) AS cuplikan_teks,
m.key_id
FROM message m
JOIN chat c ON c._id = m.chat_row_id
JOIN jid gj ON gj._id = c.jid_row_id
LEFT JOIN jid sj ON sj._id = m.sender_jid_row_id
LEFT JOIN message_forwarded mf ON mf.message_row_id = m._id
WHERE gj.raw_string = '120363012345678901@g.us'
ORDER BY m.timestamp ASC;
5. forward_score: kolom paling penting di kasus hoax
Tabel message_forwarded hanya punya dua kolom, dan salah satunya menentukan arah investigasi:
SELECT mf.forward_score,
COUNT(*) AS jumlah
FROM message_forwarded mf
GROUP BY mf.forward_score
ORDER BY mf.forward_score;
Cara membacanya:
- Tidak ada baris di
message_forwarded→ pesan diketik atau diunggah langsung di perangkat itu. Ini kandidat kuat untuk titik asal. forward_score= 1 → pesan diteruskan satu kali. Pengirim menerimanya dari orang lain.forward_score≥ 4 → WhatsApp menandainya "diteruskan berkali-kali" dan membatasi penerusan ke satu chat sekaligus. Konten sudah beredar jauh.
Cari kandidat titik asal di seluruh database:
SELECT datetime(m.timestamp/1000, 'unixepoch', '+7 hours') AS waktu_wib,
gj.raw_string AS grup,
COALESCE(sj.user, 'PEMILIK PERANGKAT') AS pengirim,
substr(m.text_data, 1, 100) AS cuplikan
FROM message m
JOIN chat c ON c._id = m.chat_row_id
JOIN jid gj ON gj._id = c.jid_row_id
LEFT JOIN jid sj ON sj._id = m.sender_jid_row_id
LEFT JOIN message_forwarded mf ON mf.message_row_id = m._id
WHERE gj.server = 'g.us'
AND mf.message_row_id IS NULL -- tidak diteruskan
AND m.text_data LIKE '%kata_kunci_hoax%'
ORDER BY m.timestamp ASC;
Peringatan besar:
forward_scorebukan bukti kepengarangan. Seseorang bisa menyalin-tempel teks dari chat lain, mengunduh gambar lalu mengunggahnya ulang dari galeri, atau mengetik ulang isi pesan. Semua itu menghasilkan pesan tanpa flag penerusan, seolah-olah orisinal. Skor penerusan mempersempit daftar kandidat; ia tidak menutup kasus.
6. Media dan hash lintas perangkat
message_media menyimpan hash konten media, yang memungkinkan kita mencocokkan file yang sama di perangkat berbeda:
SELECT m._id,
datetime(m.timestamp/1000, 'unixepoch', '+7 hours') AS waktu_wib,
COALESCE(sj.user, 'PEMILIK PERANGKAT') AS pengirim,
mm.file_path,
mm.mime_type,
mm.file_size,
mm.file_hash,
mm.media_name
FROM message_media mm
JOIN message m ON m._id = mm.message_row_id
LEFT JOIN jid sj ON sj._id = m.sender_jid_row_id
JOIN chat c ON c._id = m.chat_row_id
JOIN jid gj ON gj._id = c.jid_row_id
WHERE gj.raw_string = '120363012345678901@g.us'
AND mm.mime_type LIKE 'image/%'
ORDER BY m.timestamp ASC;
file_hash disimpan dalam base64. Untuk membandingkannya dengan hasil shasum, konversi dulu:
echo "hFmM6oPtvB1eXW2kQ..." | base64 -d | xxd -p -c 64
Kalau file_hash sebuah gambar identik di lima perangkat, kelimanya memegang berkas yang persis sama — indikasi kuat rantai penerusan, bukan pengunggahan ulang secara terpisah.
7. Pesan yang dihapus
Ada tiga jalur, dari yang paling mudah:
Backup harian. Bandingkan database aktif dengan backup dua hari lalu:
sqlite3 msgstore-S04.db \
"SELECT key_id FROM message ORDER BY key_id;" > aktif.txt
sqlite3 decrypted-msgstore-2026-08-12.1.db \
"SELECT key_id FROM message ORDER BY key_id;" > backup.txt
comm -13 aktif.txt backup.txt > hilang-sejak-12-agustus.txt
wc -l hilang-sejak-12-agustus.txt
Setiap key_id di file itu adalah pesan yang ada pada 12 Agustus tapi tidak ada sekarang. Ambil isinya dari backup.
Kutipan pesan. Kalau ada yang membalas pesan yang kemudian dihapus, isinya masih tersimpan:
SELECT datetime(m.timestamp/1000, 'unixepoch', '+7 hours') AS waktu_balasan,
mq.key_id AS key_id_pesan_asli,
mq.from_me AS asli_dari_perangkat_ini,
jq.user AS pengirim_asli,
mq.text_data AS ISI_PESAN_YANG_DIKUTIP
FROM message_quoted mq
JOIN message m ON m._id = mq.message_row_id
LEFT JOIN jid jq ON jq._id = mq.sender_jid_row_id
WHERE mq.text_data IS NOT NULL
ORDER BY m.timestamp;
Ini sering menjadi cara paling elegan memulihkan pesan yang sudah ditarik oleh pengirimnya — karena salinannya tersimpan di baris orang lain.
Carving halaman bebas SQLite. Baris yang dihapus sering masih ada di freelist dan area unallocated:
brew install undark # atau kompilasi dari sumber
undark -i msgstore-S04.db --freespace > carved-freespace.txt
grep -a -i "kata_kunci_hoax" carved-freespace.txt
Data hasil carving tidak punya konteks: tidak ada timestamp yang bisa dipercaya, tidak ada pengirim yang pasti. Perlakukan sebagai indikasi, dan selalu tandai di laporan sebagai temuan berkeyakinan rendah.
8. Sisi iOS: ChatStorage.sqlite
Struktur iOS mengikuti Core Data, jadi penamaannya berbeda total.
| Entitas | Fungsi |
|---|---|
ZWAMESSAGE |
Pesan individual |
ZWACHATSESSION |
Percakapan, termasuk nama grup |
ZWAGROUPMEMBER |
Anggota grup — penting untuk mengetahui pengirim |
ZWAMEDIAITEM |
Metadata media |
Perbedaan waktu yang wajib diingat: Core Data menghitung detik sejak 1 Januari 2001 UTC, bukan 1970. Selisihnya 978.307.200 detik.
SELECT datetime(ZMESSAGEDATE + 978307200, 'unixepoch', '+7 hours') AS waktu_wib
FROM ZWAMESSAGE LIMIT 5;
Lupa menambahkan konstanta itu akan menggeser seluruh timeline Anda mundur 31 tahun — kesalahan yang mencolok, tapi versi halusnya (menambahkannya dua kali, atau menerapkannya pada database Android) jauh lebih sulit terdeteksi.
Timeline satu grup di iOS:
SELECT m.Z_PK,
datetime(m.ZMESSAGEDATE + 978307200, 'unixepoch', '+7 hours') AS waktu_wib,
CASE m.ZISFROMME WHEN 1 THEN 'PEMILIK PERANGKAT'
ELSE COALESCE(gm.ZMEMBERJID, m.ZFROMJID) END AS pengirim,
m.ZMESSAGETYPE,
substr(m.ZTEXT, 1, 120) AS cuplikan_teks,
m.ZSTANZAID
FROM ZWAMESSAGE m
JOIN ZWACHATSESSION cs ON cs.Z_PK = m.ZCHATSESSION
LEFT JOIN ZWAGROUPMEMBER gm ON gm.Z_PK = m.ZGROUPMEMBER
WHERE cs.ZCONTACTJID = '120363012345678901@g.us'
ORDER BY m.ZMESSAGEDATE ASC;
ZSTANZAID pada iOS setara dengan key_id pada Android — inilah jembatan lintas platform Anda.
Keterbatasan iOS: skor penerusan tidak terekspos sebersih
message_forwardeddi Android. Kalau grup yang Anda selidiki punya minimal satu anggota pengguna Android yang bersedia diperiksa, prioritaskan perangkat itu untuk analisis penerusan. Ini pertimbangan praktis yang layak memengaruhi urutan akuisisi Anda.
9. Otomatiskan dengan parser siap pakai
Untuk melihat percakapan dalam bentuk yang mudah dibaca — berguna saat menjelaskan ke kepala sekolah yang tidak membaca SQL:
wtsexporter --help # periksa flag versi terpasang
# Android, dari backup terenkripsi
wtsexporter -a -k ~/cases/.../S04-backup.key -b msgstore.db.crypt15
# iOS, dari backup yang sudah didekripsi
wtsexporter -i -b $CASE/02-working/EV-003-decrypted/
ALEAPP dan iLEAPP memberi cakupan lebih luas — bukan hanya WhatsApp, tapi juga riwayat notifikasi, riwayat aplikasi, dan artefak sistem lain yang bisa mengonfirmasi aktivitas pada jam tertentu:
cd ~/forensics-tools/ALEAPP
python aleapp.py -t fs \
-i $CASE/02-working/EV-002-android-S04 \
-o $CASE/05-tools-output/aleapp-S04
Parser otomatis bagus untuk cakupan dan penyajian, tapi laporan Anda tetap harus mencantumkan kueri SQL mentah untuk setiap temuan kunci. "Tool bilang begitu" bukan metodologi; "berikut kuerinya, silakan jalankan sendiri" adalah metodologi.
10. Ekspor ke CSV untuk penggabungan
Setiap perangkat menghasilkan satu CSV dengan skema seragam, siap digabung di bagian kelima:
sqlite3 -header -csv msgstore-S04.db <<'SQL' > $CASE/03-analysis/S04-timeline.csv
SELECT 'S-04' AS device,
m.key_id AS msg_id,
strftime('%Y-%m-%dT%H:%M:%S', m.timestamp/1000, 'unixepoch') AS utc,
gj.raw_string AS group_jid,
c.subject AS group_name,
CASE m.from_me WHEN 1 THEN 'S-04' ELSE COALESCE(sj.user,'system') END AS sender,
m.message_type AS msg_type,
COALESCE(mf.forward_score, 0) AS fwd_score,
CASE WHEN mf.message_row_id IS NULL THEN 0 ELSE 1 END AS is_forwarded,
mm.file_hash AS media_hash,
replace(substr(COALESCE(m.text_data,''),1,200), char(10), ' ') AS text
FROM message m
JOIN chat c ON c._id = m.chat_row_id
JOIN jid gj ON gj._id = c.jid_row_id
LEFT JOIN jid sj ON sj._id = m.sender_jid_row_id
LEFT JOIN message_forwarded mf ON mf.message_row_id = m._id
LEFT JOIN message_media mm ON mm.message_row_id = m._id
WHERE gj.server = 'g.us'
ORDER BY m.timestamp;
SQL
Perhatikan bahwa kolom waktu disimpan dalam UTC, bukan WIB. Konversi ke waktu lokal dilakukan sekali saja, di tahap penyajian. Menyimpan waktu lokal di data mentah adalah cara paling andal membuat kesalahan yang tidak akan Anda sadari.
Kesimpulan dan langkah berikutnya
Yang seharusnya Anda punya sekarang: satu CSV per perangkat dengan skema seragam dalam UTC, daftar pesan tanpa flag penerusan sebagai kandidat titik asal, daftar hash media untuk pencocokan lintas perangkat, dan hasil pemulihan pesan terhapus lengkap dengan tingkat keyakinannya.
Yang belum terjawab: dari mana gambar dan videonya berasal, dan apakah kontennya memang benar-benar hoax.
Pada bagian keempat kita membedah medianya — konvensi penamaan file WhatsApp dan apa artinya, apa yang tersisa dari EXIF setelah kompresi WhatsApp, perceptual hashing untuk mencocokkan gambar yang sudah dikompres ulang, ekstraksi frame video dengan ffmpeg, dan verifikasi apakah konten itu sebenarnya daur ulang dari peristiwa lain.
Post Terkait
Forensik Digital Kasus Hoax Sekolah #5
Bagian penutup: menggabungkan timeline lintas perangkat, membangun graf penyebaran dengan networkx, menguji hipotesis al...
Forensik Digital Kasus Hoax Sekolah #4
Bagian keempat: membaca konvensi penamaan file WhatsApp, apa yang tersisa dari EXIF setelah kompresi, perceptual hashing...
Forensik Digital Kasus Hoax Sekolah #2
Bagian kedua: cara mengambil bukti dari ponsel siswa tanpa merusaknya. Isolasi perangkat, akuisisi logis Android tanpa r...