Forensik Dokumen PDF: Membongkar Surat Resmi Palsu
Sebuah PDF jauh lebih cerewet daripada yang diduga pembuatnya. Panduan teknis membedah dokumen resmi palsu dengan perkakas open source: membaca metadata yang ditinggalkan aplikasi desain, memisahkan lapisan penyusun halaman, membuktikan kertasnya tidak pernah ada, dan memulihkan draft versi sebelumnya yang tersembunyi di balik crop — 92 persen data yang dikira sudah terbuang. Ditutup dengan bagian yang paling sering dilewatkan: batas jujur tentang apa yang tidak bisa dijawab sebuah PDF.
Sebuah surat pemberitahuan berkop resmi beredar di grup pesan instan. Berlogo, bernomor, bertanda tangan, berstempel. Lembaga yang namanya tercantum menyatakan tidak pernah menerbitkannya.
Pertanyaannya kemudian bukan lagi "apakah ini palsu", melainkan bagaimana membuktikannya secara metodis — dan sejauh mana berkas itu sendiri bisa bercerita tentang siapa yang menyusunnya.
Kabar baiknya: sebuah PDF jauh lebih cerewet daripada yang diduga pembuatnya. Ia menyimpan aplikasi apa yang dipakai, jam berapa diekspor, berapa lapis penyusunnya, dan — pada pola yang akan kita bedah di sini — draft versi sebelumnya yang lupa dibersihkan.
Tulisan ini memakai perkakas open source saja, dan seluruh langkahnya dapat Anda ulangi sendiri.
Sebelum menyentuh berkas
Dua hal harus beres lebih dulu, dan keduanya bukan soal teknis.
Kejelasan mandat. Pastikan Anda memang berwenang memeriksa. Bila dugaannya menjurus ke pidana, keterlibatan penegak hukum sejak awal justru melindungi nilai bukti — bukan memperlambat. Bila pemeriksaan bersifat internal, catat mandat itu tertulis.
Log pemeriksa. Satu berkas teks, dicatat manual: jam berapa Anda melakukan apa, dengan perintah apa, dan apa hasilnya. Terdengar remeh, tetapi inilah yang membedakan temuan yang bisa dipertahankan dari temuan yang bisa dipatahkan dengan satu pertanyaan.
Perkakas yang dipakai tersedia di macOS maupun Linux:
brew install poppler # pdfinfo, pdftotext, pdfimages, pdftoppm, pdffonts
pip3 install pillow numpy # analisis piksel
# opsional: qpdf, mutool untuk pembacaan struktur alternatif
1. PDF bukan gambar — ia wadah berlapis
Kekeliruan paling mendasar adalah memperlakukan PDF seperti foto: satu bidang datar yang tinggal dilihat. PDF sebenarnya lebih dekat ke berkas proyek desain. Di dalamnya ada daftar objek bernomor: halaman, aliran teks, gambar, font, profil warna, metadata, dan sebuah "naskah panggung" berisi instruksi menggambar.
Konsekuensinya besar untuk pemeriksa: apa yang tampak di layar hanyalah hasil akhir komposisi. Bahan-bahan penyusunnya masih ada di dalam berkas, lengkap, termasuk bagian yang sengaja ditutupi. Itulah celah yang akan kita masuki.
2. Amankan dulu, baru periksa
Urutannya tidak boleh dibalik. Hitung hash sebelum berkas disentuh aplikasi apa pun — membuka PDF dengan sebagian pembaca dokumen sudah cukup untuk mengubah atribut berkas.
BB="berkas-bukti.pdf"
shasum -a 256 "$BB"; shasum -a 1 "$BB"; md5 "$BB"
Sebelum menyalin, baca extended attributes. Di macOS, ini artefak yang paling sering terlewat padahal sangat berharga:
xattr -l "$BB"
Keluarannya kira-kira begini:
com.apple.metadata:kMDItemWhereFroms: bplist00_https://web.whatsapp.com/
com.apple.quarantine: 0281;68f2a1c4;Chrome;9C3D40A2-11E7-4B8A-9F02-7D5E1C4A88B3
Dua baris itu memberi tahu dari mana dan dengan apa berkas masuk ke komputer pemeriksa. Angka 68f2a1c4 pada baris karantina adalah stempel waktu heksadesimal:
python3 -c "import datetime as d; t=int('68f2a1c4',16); \
print(d.datetime.fromtimestamp(t, d.timezone.utc).astimezone())"
Perlu ditegaskan sejak awal, karena sering disalahpahami di ruang rapat: ini jejak penerimaan, bukan jejak pembuatan. Ia membuktikan kapan berkas sampai ke tangan Anda — bukan kapan atau oleh siapa ia dibuat.
Setelah itu, salin dan segel:
mkdir -p KASUS/{00_asli,01_kerja,02_ekstrak,03_laporan,04_log}
cp -p "$BB" KASUS/00_asli/BB-001.pdf
cp -p "$BB" KASUS/01_kerja/salinan-kerja.pdf
chmod 444 KASUS/00_asli/BB-001.pdf
chflags uchg KASUS/00_asli/BB-001.pdf # Linux: chattr +i
shasum -a 256 KASUS/00_asli/BB-001.pdf KASUS/01_kerja/salinan-kerja.pdf
Seluruh pekerjaan berikutnya dilakukan pada salinan kerja. Barang bukti asli tidak disentuh lagi.
3. Apa yang dikatakan berkas tentang dirinya
pdfinfo -meta KASUS/01_kerja/salinan-kerja.pdf
Keluaran yang khas untuk dokumen semacam ini:
dc:language="id-ID"
pdf:Producer="Canva"
xmp:CreatorTool="Canva"
pdf:Keywords="DAFq7xR-pMk,BAE2mn9TvQc"
xmp:CreateDate="2026-07-14T02:14:22.310Z"
xmp:ModifyDate="2026-07-14T02:14:22.310Z"
<dc:title>Surat Pemberitahuan.pdf_20260714_083105_0000</dc:title>
<dc:creator><rdf:Seq><rdf:li>AK-01</rdf:li></rdf:Seq></dc:creator>
Enam hal yang langsung terbaca:
| Field | Nilai | Artinya |
|---|---|---|
Producer / CreatorTool |
aplikasi desain berbasis web | Bukan pengolah kata, bukan hasil pindai. Dokumen dirancang |
dc:creator |
nama akun | Pada banyak layanan desain, field ini diisi otomatis dari nama pemilik akun, bukan diketik pengguna |
Keywords |
dua pengenal desain | Pengenal unik desain di server penyedia layanan — permanen dan terikat akun |
CreateDate = ModifyDate |
identik | Berkas diekspor sekali, tidak disunting ulang setelah keluar dari aplikasi |
dc:language |
id-ID |
Antarmuka aplikasi atau peramban penyusun berbahasa Indonesia |
dc:title |
memuat nama berkas induk beserta stempel waktu | Ada berkas lain sebelum ini. Catat baik-baik |
Poin terakhir sering dilewati. Judul dokumen masih menyimpan nama berkas pendahulunya, lengkap dengan pola _YYYYMMDD_HHMMSS_0000. Sufiks _0000 adalah nomor halaman — ciri khas aplikasi yang mengubah dokumen menjadi gambar per halaman. Artinya: sebelum PDF ini lahir, ada dokumen lain yang dirasterisasi menjadi gambar, lalu gambar itu diimpor kembali. Kita baru saja menemukan jejak iterasi.
Periksa juga struktur kasarnya:
grep -a -c "%%EOF" salinan-kerja.pdf # >1 berarti ada incremental update
grep -a -o "/ID *\[[^]]*\]" salinan-kerja.pdf
Satu %%EOF berarti berkas tidak pernah disunting ulang oleh editor PDF setelah diekspor. Dua nilai /ID yang identik menguatkan hal yang sama. Ini mempersempit tempat penyuntingan terjadi ke satu aplikasi saja.
4. Bedah lapisannya
pdffonts salinan-kerja.pdf
pdfimages -list salinan-kerja.pdf
pdftotext -layout salinan-kerja.pdf teks.txt
Hasilnya: satu font tertanam dan dua objek gambar. Kombinasi ini sudah bercerita. Kalau dokumen benar-benar hasil pindai lembar fisik, tidak akan ada teks hidup sama sekali — semuanya piksel. Kalau ia dokumen kantor asli, tidak perlu ada gambar sebesar halaman.
Yang kita hadapi campuran: teks hidup diketik di atas gambar. Mari lihat naskah panggungnya.
python3 - <<'PY'
import re, zlib
d = open('salinan-kerja.pdf','rb').read()
def stream(num):
m = re.search(rb'\n%d\s+0\s+obj(.*?)stream\r?\n' % num, d, re.S)
hdr = m.group(1); s = d[m.end():d.find(b'endstream', m.end())]
return zlib.decompress(s) if b'FlateDecode' in hdr else s
print(stream(20).decode('latin-1')) # nomor objek dari /Contents halaman
PY
Di dalamnya terbaca dua pemanggilan gambar, /X6 Do dan /X8 Do, masing-masing dibungkus operator q … Q dengan matriks transformasinya sendiri:
| Lapisan | Isi | Catatan |
|---|---|---|
| 1 (bawah) | Gambar kop, dibentang seukuran halaman | Bertekstur, ada pergeseran warna — berasal dari pindaian atau foto |
| 2 (tengah) | Teks, font Times New Roman tertanam | Diketik langsung di aplikasi desain |
| 3 (atas) | Gambar kecil berisi blok tanda tangan dan stempel | Di sinilah kejanggalannya |
5. Temuan yang membalik arah pemeriksaan
Perhatikan angka pada lapisan ketiga. Objek gambarnya berukuran 912 × 1140 piksel — terlalu besar untuk sekadar tanda tangan. Lalu lihat instruksi menggambarnya:
q
/Artifact BMC
0 0 921.4 640.0 re
W* n
EMC
q
2480.5 0 0 -3100.6 -1062.4 902.3 cm
/X7 Do
Q
Q
Baris re diikuti W* n adalah clipping path: jendela sempit tempat gambar boleh terlihat. Baris cm adalah matriks transformasi. Bandingkan keduanya: gambar digambar seluas 2480 × 3100 satuan, tetapi jendelanya hanya 921 × 640 satuan, dan titik asalnya digeser ke koordinat negatif.
Artinya sebagian besar gambar itu ada di dalam berkas, tetapi berada di luar jendela. Mari hitung persisnya.
sx, sy = 2480.5, 3100.6 # skala dari matriks cm
ox, oy = -1062.4, 902.3 # pergeseran dari matriks cm
W, H = 912, 1140 # ukuran piksel gambar
cw, ch = 921.4, 640.0 # ukuran jendela clip
u0, u1 = (0 - ox)/sx, (cw - ox)/sx # sumbu X: 0..1 kiri ke kanan
v_bwh = (oy - ch)/sy # sumbu Y citra diukur dari BAWAH
v_atas = (oy - 0)/sy # (skala Y negatif = citra terbalik)
kol = int(u0*W), int(u1*W)
bar = int((1-v_atas)*H), int((1-v_bwh)*H) # ubah ke baris dari ATAS
tampak = (kol[1]-kol[0]) * (bar[1]-bar[0]) / (W*H)
print("kolom tampak :", kol) # (390, 729)
print("baris tampak :", bar) # (808, 1043)
print("luas tampak : %.1f%%" % (tampak*100)) # 7.7%
print("tersembunyi : %.1f%%" % (100 - tampak*100)) # 92.3%
Tujuh koma tujuh persen tampak. Sembilan puluh dua koma tiga persen tersembunyi.
Satu jebakan yang wajib diingat: pada PDF, sumbu Y citra diukur dari bawah, dan matriks di atas memakai skala Y negatif sehingga gambar terbalik. Bila Anda lupa mengonversinya menjadi baris-dari-atas, hasil perhitungan akan menunjuk ke bagian kop, bukan ke blok tanda tangan — dan kesimpulan Anda meleset jauh.
Sekarang keluarkan gambar utuhnya:
pdfimages -all -p salinan-kerja.pdf KASUS/02_ekstrak/img
Yang muncul bukan potongan tanda tangan, melainkan satu halaman utuh dari versi dokumen yang lebih awal — dari kop sampai baris penutup.
Penyusunnya hanya butuh blok tanda tangan dari draft lamanya. Ia memotongnya lewat fitur crop, dan crop pada aplikasi desain tidak membuang data — ia hanya memasang jendela. Sisa 92,3% ikut terekspor ke dalam PDF, utuh dan terbaca oleh siapa pun yang tahu cara mengambilnya.
6. Membandingkan dua versi
Ini bagian dengan nilai pembuktian tertinggi. Sandingkan draft yang dipulihkan dengan versi yang beredar:
| Draft versi-1 (tersembunyi) | Versi final (beredar) |
|---|---|
| tanggal berlaku dibiarkan kosong | tanggal sudah diisi |
| satu kata ditulis huruf kapital semua | kapitalisasi diperbaiki |
| dua salah ketik pada satu kalimat | keduanya sudah dikoreksi |
| tidak ada kalimat penutup tambahan | satu kalimat baru ditambahkan |
| kop versi digital tajam | kop diganti versi hasil pindai |
Empat kesimpulan mengalir dari tabel sederhana ini:
- Terjadi penyuntingan berulang. Ada v-1, lalu ada v-2.
- Penyusunnya menguasai berkas sumber yang dapat diedit. Orang yang sekadar meneruskan berkas milik orang lain tidak mungkin menghasilkan artefak seperti ini. Ini membedakan pembuat dari penyebar — perbedaan yang menentukan konsekuensi hukumnya.
- Ada kesengajaan. Memperbaiki salah ketik dan menambah kalimat adalah tindakan sadar agar dokumen lebih meyakinkan.
- Penyusunnya tidak memiliki aset tanda tangan dan stempel secara terpisah. Satu-satunya salinan yang ia punya sudah menyatu di dalam render v-1, sehingga ia terpaksa memotongnya kembali dari sana. Ini memberi tahu sesuatu tentang seberapa terbatas bahan yang ia miliki.
7. Kertas yang tidak pernah ada
Apakah pernah ada lembar fisik yang benar-benar ditandatangani lalu dipindai? Uji ini murah dan sangat menentukan.
from PIL import Image
import numpy as np
a = np.array(Image.open('img-001-001.jpg').convert('L')).astype(float)
kosong = a[960:1010, 150:700] # area kertas yang tak ada tulisannya
print(kosong.mean(), kosong.std(), kosong.min())
Hasilnya: rerata 255,0, standar deviasi 0,00, nilai minimum 255.
Kertas sungguhan — dipindai maupun difoto — selalu punya derau: serat, gradasi pencahayaan, sedikit pergeseran warna. Standar deviasi nol berarti setiap piksel bernilai putih maksimum yang identik. Itu bukan kertas. Itu kanvas digital.
Pada lapisan kop, ujilah sebaran piksel non-putihnya:
rgb = np.array(Image.open('img-001-000.jpg').convert('RGB')).astype(int)
nonputih = rgb.mean(axis=2) < 254.5
baris = np.nonzero(nonputih.sum(axis=1))[0]
print(baris.min(), baris.max(), "dari", rgb.shape[0])
Keluarannya menunjukkan seluruh isi gambar hanya menempati baris teratas, sekitar 13% tinggi gambar. Sisanya putih mutlak. Jadi lapisan latar itu bukan pindaian satu halaman penuh, melainkan potongan kop yang ditempel di atas kanvas kosong.
8. Uji kompositing tanda tangan dan stempel
Sekarang periksa apakah tanda tangan dan stempel benar-benar tinta di atas kertas, atau dua tempelan digital.
reg = np.array(Image.open('img-001-001.jpg').convert('RGB')).astype(int)[740:890, 380:670]
r, g, b = reg[:,:,0], reg[:,:,1], reg[:,:,2]
L = reg.mean(axis=2)
ungu = (b-g > 12) & (r-g > 8) & (L < 215)
gelap = (L < 110) & (abs(b-g) < 18) & (abs(r-g) < 18)
print("stempel:", r[ungu].mean(), g[ungu].mean(), b[ungu].mean())
print("ttd :", r[gelap].mean(), g[gelap].mean(), b[gelap].mean())
Tiga kejanggalan muncul bersamaan:
- Stempel semi-transparan, tanda tangan opak. Teks di bawah cincin stempel masih terbaca menembusnya — ciri komposit multiply. Guratan tanda tangan justru menutup total apa pun di bawahnya. Dua mode penggabungan berbeda pada satu dokumen yang seharusnya satu lembar kertas.
- Ketajaman tepi tidak sepadan. Ukur gradien tepi dengan
np.diff: tepi teks cetak mencapai nilai jauh lebih tinggi daripada guratan tanda tangan. Tanda tangan itu potongan beresolusi rendah yang diperbesar. - Rotasi bersih. Stempel miring pada sudut rapi tanpa distorsi perspektif sama sekali. Stempel karet sungguhan hampir selalu meninggalkan ketebalan tinta yang tidak rata dan bagian yang putus.
Sendiri-sendiri, tiap poin bisa dibantah. Bersama-sama, ketiganya konsisten hanya dengan satu penjelasan: overlay digital.
9. Menyimpulkan waktu — dan sedikit tentang zona waktu
Banyak layanan berbasis web menormalkan stempel waktu ke UTC (+00'00'), sehingga zona waktu perangkat penyusun tidak bocor langsung. Tetapi ingat nama berkas induk dari langkah 3: stempel _20260714_083105_ itu dibuat oleh jam lokal perangkat, bukan oleh server.
Dari sini bisa dibangun batas logis. Berkas v-1 wajib lahir sebelum ekspor v-2 (02:14:22 UTC). Uji tiap kemungkinan zona:
| Zona perangkat | 08:31:05 lokal = UTC | Konsisten? | Jeda penyuntingan |
|---|---|---|---|
| UTC+5 | 03:31:05 | tidak — mustahil | negatif |
| UTC+6 | 02:31:05 | tidak — mustahil | negatif |
| UTC+7 | 01:31:05 | ya | 43 menit — paling wajar |
| UTC+8 | 00:31:05 | ya | 1 jam 43 menit |
| UTC+9 | 23:31:05 (H-1) | ya | 2 jam 43 menit |
Zona UTC+6 ke bawah tereliminasi sepenuhnya, dan UTC+7 memberi jeda penyuntingan paling masuk akal. Ini bukan bukti keras, melainkan inferensi yang dapat diuji ulang — dan itulah standar yang harus dipegang. Tulis dalam laporan sebagai inferensi, bukan sebagai fakta.
10. Batas jujur: apa yang PDF tidak bisa katakan
Bagian ini sama pentingnya dengan seluruh temuan di atas, dan justru paling sering diabaikan ketika pimpinan sudah telanjur bersemangat.
| Yang sering ditanyakan | Jawaban jujur |
|---|---|
| Alamat IP | Tidak ada. PDF tidak pernah menyimpannya |
| Alamat MAC atau nomor seri perangkat | Tidak ada |
| Merek, model, atau sistem operasi perangkat | Tidak ada. Layanan desain web merender di server; jejak perangkat klien tidak ikut |
| Koordinat GPS | Tidak ada. EXIF terhapus saat layanan mengompresi ulang gambar |
| Identitas printer atau pemindai | Tidak ada. Dokumen tidak pernah dicetak |
Satu jebakan khusus: berkas semacam ini memuat font Times New Roman versi tertentu dari Monotype. Menggoda sekali untuk menyimpulkan sistem operasi penyusunnya dari situ. Jangan. Font itu disediakan oleh server layanan desain, bukan diambil dari komputer penyusun. Profil warna sRGB di dalamnya juga bawaan layanan. Kesimpulan yang ditarik dari keduanya akan salah.
Periksa juga kemungkinan data tersisip, lalu catat hasilnya walau nihil:
tail -c 64 salinan-kerja.pdf | xxd # data setelah %%EOF?
python3 -c "d=open('img.jpg','rb').read(); print(len(d)-d.rfind(b'\xff\xd9')-2)"
Temuan negatif tetap temuan. Menuliskannya membuat laporan Anda lebih kredibel, bukan lebih lemah.
11. Nama di metadata bukan vonis
Bila field pembuat berisi sebuah nama, Anda memegang petunjuk kuat — bukan kesimpulan. Setidaknya empat penjelasan lain harus disingkirkan lebih dulu:
- Nama tampilan akun dapat diganti kapan saja oleh pemiliknya.
- Akun bisa dipinjam, dipakai bersama, atau diambil alih.
- Bila desain dikerjakan kolaboratif, nama yang tercatat bisa jadi pemilik desain, bukan orang yang menekan tombol ekspor.
- Seseorang bisa sengaja memakai akun orang lain justru agar tuduhan jatuh ke sana.
Perlakukan nama itu sebagai hipotesis yang wajib dikorroborasi, dan tulislah demikian di laporan. Jarak antara "petunjuk kuat" dan "vonis" adalah jarak antara tindakan yang tepat sasaran dan seseorang yang dirugikan oleh kesimpulan terburu-buru.
Korroborasi yang benar-benar menutup pemeriksaan justru datang dari luar berkas:
- Telusuri mundur rantai penerusan. Kumpulkan tangkapan layar dari sebanyak mungkin penerima, lalu cari pengirim paling awal yang pesannya tanpa label "Diteruskan".
- Cari sumber asetnya. Kop, tanda tangan, dan stempel pasti berasal dari suatu dokumen asli. Bandingkan piksel per piksel dengan dokumen resmi yang pernah terbit. Bila cocok persis — rotasi sama, celah tinta sama — daftar penerima dokumen itu langsung mempersempit lingkaran secara drastis.
- Periksa buku agenda surat keluar. Apakah nomor surat pada dokumen palsu benar-benar ada? Bila ada, siapa saja yang pernah memegangnya?
- Data akun pada penyedia layanan hanya terbuka lewat permintaan resmi penegak hukum. Organisasi korban tidak bisa dan tidak boleh memintanya sendiri.
Penutup
Yang membongkar dokumen semacam ini bukan perkakas mahal, melainkan kesediaan membaca berkas sebagai struktur, bukan sebagai gambar — dan satu kebiasaan sederhana: ketika ada objek yang dipotong, selalu keluarkan versi utuhnya.
Crop pada aplikasi desain modern hampir tidak pernah membuang data. Ia memasang tirai. Dan tirai, berbeda dari gunting, bisa dibuka kembali.
Tiga hal untuk dibawa pulang:
- Amankan sebelum memeriksa. Hash dulu, baca extended attributes dulu, baru salin dan segel.
- Pisahkan pembuat dari penyebar. Jejak iterasi — draft tersembunyi, nama berkas induk, selisih stempel waktu — adalah yang membedakan keduanya, dan perbedaan itu menentukan konsekuensinya.
- Nyatakan batas Anda. PDF tidak menyimpan IP, MAC, GPS, atau identitas perangkat. Laporan yang jujur tentang apa yang tidak diketahuinya jauh lebih berguna daripada laporan yang terdengar meyakinkan tetapi keliru.
Ada satu pelajaran tambahan yang tidak teknis, bagi siapa pun yang mengelola dokumen resmi: pemalsuan semacam ini memakai bahan dari dokumen resmi Anda sendiri yang sudah beredar. Kop beresolusi tinggi, tanda tangan, dan stempel yang tersebar dalam bentuk gambar adalah bahan baku yang siap pakai. Penomoran yang dapat diverifikasi, satu kanal pengumuman resmi, dan kebiasaan mengarahkan penerima untuk mengonfirmasi lewat kanal itu jauh lebih murah daripada memeriksa setelah kejadian.
Seluruh nilai contoh dalam tulisan ini — nama berkas, hash, pengenal desain, angka piksel, dan stempel waktu — bersifat ilustratif dan tidak merujuk pada berkas mana pun. Tulisan ini bersifat edukatif dan bukan nasihat hukum; untuk perkara yang berpotensi pidana, libatkan penegak hukum dan penasihat hukum sejak awal.
Post Terkait
Web Application Security Fundamentals #14: Security Mindset dan Checklist
Satukan asset, threat model, secure design, implementation, testing, deployment, monitoring, dan incident learning dalam...
Web Application Security Fundamentals #13: Dampak Vulnerability bagi Bisnis
Hubungkan vulnerability dengan data breach, fraud, downtime, recovery cost, reputasi, dan kewajiban melalui risk scenari...
Web Application Security Fundamentals #12: Secure Coding Practices
Terapkan parameterization, context encoding, centralized authorization, secret management, safe error handling, dependen...