İçeriğe atla
İlyas Saltay

Yazılar · PDF · e-İmza · Belgeler

e-İmzalı PDF açılmıyor: e-Devlet ya da TSE'den inen dosya PDF değil, PKCS#7 imza zarfı olabilir

5 dakika okumaİlyas Saltay

Kısa cevap

Uzantısı .pdf olan ama hiçbir okuyucunun açmadığı kurum belgelerinin çoğu bozuk değil, e-imza zarfıdır: dosya 30 82 ya da 30 83 baytlarıyla başlar, hemen ardından PKCS#7 signedData kimliği 2A 86 48 86 F7 0D 01 07 02 gelir ve gerçek PDF bu zarfın içinde durur. Önbellek temizlemek, tarayıcı değiştirmek işe yaramaz. Çıkarmanın doğru yolu openssl cms -verify -noverify -binary -inform DER; OpenSSL yoksa PowerShell ile %PDF- ile son %%EOF arasını kesmek de çalışır. Çıkan PDF görsel olarak eksiksizdir ama imzanın doğrulanabilirliği zarfta kalır; resmî ibraz için orijinal dosyayı sakla.

Temmuz sonunda bir müşterinin TSE Hizmet Yeterlilik Belgesi'ni sitesine koyacaktım. Belge kurumun sisteminden .PDF uzantısıyla inmişti; Chrome açmadı, Edge açmadı, PDF okuyucu "missing %PDF- header" deyip kapandı. İlk refleks belli: dosya yarım inmiştir, yeniden indir. Yeniden indirdim, aynı. Bu hatayı aratınca çıkan tavsiyelerin hepsi aynı kapıya çıkıyordu: önbelleği temizle, başka tarayıcı dene, Adobe'u güncelle. Hiçbiri sorunla ilgili değildi, çünkü dosya bozuk değildi. Dosya PDF değildi.

Otuz saniyede teşhis: dosya gerçekten PDF mi?

Bir dosyanın ne olduğunu uzantısı değil ilk baytları söyler. Windows'ta PowerShell'i açıp şunu yaz:

Format-Hex -Path .\belge.pdf | Select-Object -First 2

Mac ya da Linux'ta xxd -l 16 belge.pdf aynı işi görür. İki ihtimal var.

Gerçek bir PDF şöyle başlar; sağdaki sütunda %PDF-1. okunur:

25 50 44 46 2D 31 2E 34 0A 31 20 30 20 6F 62 6A   %PDF-1.4.1 0 obj

Dosya buysa sorun başka yerde: indirme yarım kalmıştır, okuyucu eskidir ya da telefonun varsayılan uygulaması dosyayı yanlış açıyordur. Bu yazı o durumu anlatmıyor.

İmza zarfı ise şöyle başlar; sağ sütun anlamsızdır:

30 82 06 AD 06 09 2A 86 48 86 F7 0D 01 07 02 A0   0...*H÷....

İlk bayt 30, ASN.1 dilinde bir SEQUENCE. Ardından gelen 82 ya da 83, uzunluğun kaç baytla yazıldığını söyler; dosya büyüdükçe bu sayı büyür, benim belgemde 83'tü. Asıl kimlik dokuz baytlık dizidir: 2A 86 48 86 F7 0D 01 07 02. Bu, 1.2.840.113549.1.7.2 nesne tanımlayıcısının kodlanmış hâli, yani PKCS#7 signedData. Bu diziyi gördüğün anda teşhis bitmiştir: elindeki dosya bir e-imza zarfı, gerçek PDF onun içinde.

OpenSSL kuruluysa aynı şeyi tek satırla, isimleriyle görürsün:

openssl asn1parse -inform DER -in belge.pdf | head -3
#  0:d=0  hl=4 l=1709 cons: SEQUENCE
#  4:d=1  hl=2 l=   9 prim: OBJECT   :pkcs7-signedData

Zarf nedir, neden .pdf uzantısıyla geliyor

Türkiye'de kurumların e-imza altyapısı çoğunlukla CAdES standardına dayanır. Bu standartta imza belgenin içine gömülmez; belge, imza ve imzacının sertifikası birlikte bir CMS/PKCS#7 zarfına sarılır. Zarfın standart uzantısı .p7s'dir ve e-imza programları bu uzantıyı tanır. Bazı kurum sistemleri ise imzalı çıktıya belgenin kendi adını ve uzantısını verir; e-Devlet üzerinden ya da kurumun kendi portalından dosya belge.pdf olarak iner ama içeriği PKCS#7'dir. Tarayıcı dosyayı uzantıya değil baytlara bakarak tanır, %PDF- başlığını bulamaz ve açmaz. Bu yüzden "PDF onarma" araçları da çaresizdir; onarılacak bir PDF yok, açılacak bir zarf var.

Adobe Reader'da açılan ve üstünde imza paneli görünen belgeler farklı bir standarttır, PAdES. Orada imza PDF'in içindedir ve dosya normal bir PDF gibi davranır. Elindeki dosya hiç açılmıyorsa PAdES değil, dışarıdan saran CAdES zarfıyla karşı karşıyasın.

Yol 1: OpenSSL ile zarfı aç

Doğru yol bu. Zarfı standardına göre çözer, içindeki belgeyi bayt bayt aynı şekilde verir.

openssl cms -verify -noverify -binary -inform DER -in belge.pdf -out belge-icerik.pdf

Üç bayrağın üçü de gerekli. -inform DER, dosyanın metin değil ikili kodlu olduğunu söyler. -noverify, imzacının sertifika zincirini denetlemeyi kapatır; amaç imzayı hukuken doğrulamak değil, içeriği almak. -binary ise en kritik olan: onu koymazsan OpenSSL çıktıyı S/MIME metni gibi ele alıp satır sonlarını çevirir. Bunu ölçtüm; bayraksız çıktıda her \n, \r\n olmuştu. Küçük bir dosyada belki fark etmez ama sıkıştırılmış akış içeren gerçek bir PDF bozulur.

Windows'ta OpenSSL'i ayrıca kurmana gerek yok; Git for Windows ile gelen Git Bash içinde hazır. Eski bir OpenSSL ya da macOS'un LibreSSL'i cms alt komutunu tanımazsa openssl smime -verify -noverify -binary -inform DER -in belge.pdf -out belge-icerik.pdf aynı sonucu verir.

Komut "no content" hatası verirse zarf ayrık imzadır: içinde belge yok, yalnız imza var. O zaman belgenin kendisi kurumdan ayrıca alınmalıdır; dosyanın içinden çıkarılacak bir şey yoktur.

Yol 2: PowerShell ile PDF'i kes

OpenSSL yoksa ve elinde yalnız Windows varsa, zarfın içindeki PDF'in başını ve sonunu bulup arasını kesebilirsin. PDF %PDF- ile başlar, %%EOF ile biter.

$src = ".\belge.pdf"
$dst = ".\belge-icerik.pdf"
$b   = [System.IO.File]::ReadAllBytes($src)
$lat = [System.Text.Encoding]::GetEncoding(28591)
$s   = $lat.GetString($b)
$start = $s.IndexOf('%PDF-')
$end   = $s.LastIndexOf('%%EOF') + 5
[System.IO.File]::WriteAllBytes($dst, $b[$start..($end-1)])

İki ayrıntı önemli. Dosyayı Latin-1 (kod sayfası 28591) ile metne çeviriyorum; bu kodlamada her bayt tek karaktere karşılık gelir, dolayısıyla metinde bulduğum konum bayt konumuyla aynıdır. UTF-8 ile okusaydım çok baytlı karakterler konumları kaydırırdı. İkincisi LastIndexOf: bir PDF'te birden fazla %%EOF olabilir, çünkü sonradan yapılan değişiklikler dosyanın sonuna eklenir; sınır sonuncusudur.

Bu yöntem zarf içeriğinin tek parça yazılmış olmasına güvenir. Standart DER kodlamasında öyledir, benim belgemde de öyleydi. Çıkan dosya yine açılmıyorsa içerik parçalı yazılmıştır; o durumda birinci yola dönmek gerekir.

Neyi kaybediyorsun: imzanın kendisi

Her iki yolla çıkan PDF görsel olarak eksiksizdir; sayfalar, mühürler, karekod, hepsi yerindedir. Kaybolan şey imzadır. Belgenin gerçekten o kurumdan çıktığını ve değişmediğini kanıtlayan imza zarfta kalır, kesilmiş PDF'in üstünde yoktur. Bu yüzden kural basit: orijinal dosyayı silme, sakla. Bir kuruma ibraz edeceksen zarfı ver; web sitesinde gösterecek, e-postayla paylaşacak ya da yazdıracaksan çıkardığın PDF'i kullan. Ben müşterinin sitesine PDF'i koydum, yanına da belgenin üstünde basılı olan doğrulama bağlantısını ekledim; TSE belgelerinde bu bağlantı evrakkontrol.tse.org.tr üstünden çalışır ve belgeyi kaynağından teyit ettirir.

Zarfı imzasıyla birlikte açmak istersen en kolayı dosyanın bir kopyasının uzantısını .p7s yapmaktır. E-imza sertifikanla birlikte gelen imzalama programı bu uzantıyı tanır, imzayı doğrular ve içindeki belgeyi gösterir. Teknik hiçbir şeyle uğraşmak istemiyorsan üçüncü yol da var: belgeyi veren kurumdan imzasız görüntü kopyasını iste; çoğu kurumun doğrulama sayfası belgeyi zaten tarayıcıda gösteriyor.

Bu yazı neye dayanıyor

30 Temmuz 2026, bir müşterinin TSE Hizmet Yeterlilik Belgesi. Dosya 30 83 09 32 ile başlıyordu; PowerShell yöntemiyle çıkarılan PDF siteye kondu, orijinal zarf arşivlendi. 9 Eylül 2026'da iki yolu da yeniden ölçtüm: OpenSSL 3.5 ile kendi ürettiğim bir zarfta cms ve smime komutları -binary ile orijinalin aynısını verdi, bayraksız çıktıda satır sonları değişti; Windows PowerShell 5.1 kesme yöntemi aynı zarftan PDF gövdesini eksiksiz çıkardı. Ayrık imza denemesi beklenen "no content" hatasını verdi.

İlyas Saltay
İlyas Saltay

Yazılım ve sistem mimarı, AlgowAI'da COO. Bu yazıdaki her şey canlı bir sistemde ölçülerek yaşandı; tahmin yok. Sorun sende de varsa yaz, bakarım.

Aynı sorun sende de mi var?

Sunucu, önbellek, arama görünürlüğü ya da yayın sonrası bir arıza; yazın, aynı gün dönerim.

contact@ilyassaltay.com