Hoe handtekeningen in een PDF zijn ingebed
ISO 32000-2 §12.8 Spec: ETSI EN 319 142-1 ETSI EN 319 142-1 Spec: RFC 5652 RFC 5652 Evidence: Standard-backed
In één oogopslag
Sectie met titel “In één oogopslag”Een PDF-handtekening wordt niet als een envelop om het bestand gevouwen. Ze zit in het bestand zelf: een dictionary die de handtekening beschrijft, plus een digest die wordt berekend over een opgegeven reeks bytes en de handtekeningwaarde zelf bewust overslaat. Deze pagina legt dat mechanisme uit en, minstens zo belangrijk, wat het niet belooft.
Waarom dit belangrijk is
Sectie met titel “Waarom dit belangrijk is”“Het document is ondertekend” is een zin waarop mensen beslissingen baseren. Ze verbinden er een betaling, een goedkeuring of een juridische verplichting aan. Als je niet precies weet welke bytes een handtekening dekt, kun je niet zeggen wat een geldig resultaat eigenlijk aantoont. Een PDF kan een volledig geldige handtekening bevatten en toch aan de lezer inhoud tonen die de ondertekenaar nooit heeft gezien, omdat die inhoud na het ondertekenen is toegevoegd in een gebied dat de handtekening nooit heeft gedekt. Weten waar de bewijskracht van de handtekening begint en eindigt, maakt het verschil tussen een verdedigbare beslissing en een beslissing op hoop van zegen.
Kort samengevat
Sectie met titel “Kort samengevat”- Een PDF-handtekening bevindt zich in een signature dictionary en een signature field binnen het document, niet in een externe envelop.
- De ondertekende bytes worden opgegeven door een
ByteRange-array: twee(offset, length)-segmenten die samen het hele bestand dekken behalve de hexadecimale handtekeningwaarde in deContents-entry. - De digest van die twee aaneengeschakelde segmenten is wat de cryptografische handtekening daadwerkelijk beschermt.
- Alles wat later in een nieuwe revisie wordt toegevoegd, valt buiten de oorspronkelijke byte range. De oorspronkelijke handtekening blijft geldig; die heeft nooit een uitspraak gedaan over de nieuwe bytes.
- Een approval-handtekening en een certification-handtekening verschillen in reikwijdte: certificering (DocMDP) beperkt welke latere wijzigingen zijn toegestaan; goedkeuring doet dat niet.
Hoe NextPDF dit aanpakt
Sectie met titel “Hoe NextPDF dit aanpakt”NextPDF bouwt de handtekening op volgens het model van het PDF-formaat, in een vaste volgorde, zodat de byte range exact is en geen benadering.
Bij het schrijven van een handtekening reserveert de engine eerst een slot van vaste grootte voor de
Contents-waarde en schrijft hij een ByteRange-placeholder van vaste breedte. De engine
wacht tot het volledige document is geschreven, inclusief de cross-reference
table en de end-of-file-marker. Pas dan berekent hij de twee werkelijke offsets,
schrijft die terug in de placeholder zonder ook maar één byte te verschuiven, hasht hij de twee
segmenten en plaatst hij het resulterende CMS-object in het gereserveerde slot. De
placeholder wordt met nullen aangevuld tot een constante lengte, juist zodat het invullen van de
werkelijke getallen de bytes die worden gehasht niet kan verschuiven. Dit is de enige volgorde die
een intern consistente handtekening oplevert. De engine behandelt elke fout in deze
reeks als een harde fout in plaats van een stille fallback.
Voor het PDF 2.0-profiel is het handtekeningobject zelf een losgekoppelde (detached) CMS
SignedData-structuur. De PDF-dictionary zegt waar en hoe; het CMS-object bevat het wie en het cryptografische bewijs.
- Step 1 of 4: ISO 32000-2 §12.8.1 — ByteRange digest & signature dictionary
- Step 2 of 4: ISO 32000-2 §12.8.3.3 — ETSI.CAdES.detached SubFilter
- Step 3 of 4: ETSI EN 319 142-1 PAdES baseline profile
- Step 4 of 4: RFC 5652 CMS SignedData in Contents
Wat het bewijs zegt
Sectie met titel “Wat het bewijs zegt” Evidence: Standard-backed Het mechanisme wordt gedefinieerd door
Spec: ISO 32000-2, §12.8.1 ISO 32000-2 §12.8.1 . Een byte-range-digest wordt
berekend over een reeks bytes die wordt aangeduid door de ByteRange-entry. Die reeks
moet het hele bestand omvatten, inclusief de signature dictionary maar exclusief
de handtekeningwaarde: de Contents-entry. ByteRange is een array van
integerparen — beginoffset en lengte. Niet-aaneengesloten reeksen worden
hier juist gebruikt zodat de digest de handtekeningwaarde zelf kan weglaten.
Voor het PDF 2.0-profiel specificeert Spec: ISO 32000-2, §12.8.3.3 ISO 32000-2 §12.8.3.3 dat, wanneer de SubFilter ETSI.CAdES.detached is, de Contents-waarde een DER-gecodeerd CMS SignedData-object is: dezelfde structuur die
Spec: RFC 5652 RFC 5652 definieert. Voor dat object geldt het PAdES-profiel
dat Spec: ETSI EN 319 142-1 ETSI EN 319 142-1 beschrijft.
De reikwijdte is niet voor alle handtekeningen gelijk. Spec: ISO 32000-2, §12.7.4.5 ISO 32000-2 §12.7.4.5 definieert de MDP-machtiging: een waarde van 0 maakt van de handtekening een approval-handtekening, terwijl de waarden 1–3 er een certification-handtekening van maken die beperkt welke latere wijzigingen het document conform houden. Het byte-range-mechanisme blijft hetzelfde; de belofte over latere wijzigingen is anders.
De engine van NextPDF implementeert precies dit: een ByteRange-placeholder van vaste breedte, de aaneengeschakelde digest van twee segmenten en een losgekoppeld CMS-object in een gereserveerd Contents-slot, dat pas wordt afgerond nadat het bestand compleet is.
Praktijkvoorbeeld
Sectie met titel “Praktijkvoorbeeld”Je bouwt zelden handmatig een ByteRange. Het doel van het voorbeeld is om de vorm van het resultaat te tonen, zodat je die herkent wanneer je een ondertekend bestand inspecteert.
<?php
declare(strict_types=1);
use NextPDF\Security\Signature\ByteRangeCalculator;
// Offsets the engine knows only after the whole PDF is written:// $contentsStart — byte just before the '<' of the hex signature// $contentsEnd — byte just after the '>' that closes it// $fileLength — total file size in bytes$range = ByteRangeCalculator::calculate( contentsStart: $contentsStart, contentsEnd: $contentsEnd, fileLength: $fileLength,);// $range === [0, $contentsStart, $contentsEnd, $fileLength - $contentsEnd]// Segment 1: file start → just before the signature value// Segment 2: just after the signature value → end of file// The signature value itself is the gap. It is never hashed.
$signedMessage = ByteRangeCalculator::extractSignedData($pdfBytes, $range);// $signedMessage is segment 1 concatenated with segment 2 — exactly the// bytes the cryptographic digest is computed over.De ruimte tussen de twee segmenten is de handtekeningwaarde. Die waarde kan geen deel uitmaken van haar eigen digest, en daarom bestaat de reeks uit twee delen en niet uit één.
Veelvoorkomend misverstand
Sectie met titel “Veelvoorkomend misverstand”De valkuil is dat je denkt dat een geldige handtekening betekent dat het hele bestand dat je voor je hebt is ondertekend. Dat is niet zo. Het betekent dat de bytes binnen de opgegeven reeks intact zijn. Een latere revisie kan legitiem inhoud toevoegen — een tweede handtekening, formuliergegevens, validatiemateriaal — buiten die reeks. De eerste handtekening blijft geldig en zegt niets over de toevoeging. Een correcte viewer laat zien dat een handtekening “het document zoals het bij ondertekening bestond” dekt, niet “elke byte op het scherm”. Als je die twee gelijkstelt, kan een ondertekend document niet-ondertekende inhoud bevatten die ondertekend lijkt.
Grenzen en beperkingen
Sectie met titel “Grenzen en beperkingen”Deze pagina legt de structuur uit, niet het vertrouwen. Een correct gevormde
ByteRange en een CMS-object vertellen je dat de bytes intact zijn en welke sleutel
ze heeft ondertekend. Op zichzelf vertellen ze je niet of die sleutel toebehoort aan wie je
denkt, of het bijbehorende certificaat geldig was bij ondertekening, of dat het later
is ingetrokken. Dat hoort bij certificaatpad- en intrekkingsvalidatie, die aan de orde komt in
Een handtekening correct valideren.
Deze pagina behandelt evenmin wanneer het ondertekenen volgens een
onafhankelijke autoriteit plaatsvond. Een zelfopgegeven ondertekeningstijd is geen vertrouwde tijd —
zie Tijdstempels en vertrouwde tijd.
NextPDF bouwt de hier beschreven structuur; de certificaten, trust anchors
en de tijdstempelautoriteit komen uit je deployment, niet uit de engine.
Wat de engine per niveau levert, is de mogelijkheid om de structuur op te bouwen:
| Edition | Availability |
|---|---|
| Core | PAdES B-B: de signature dictionary, de ByteRange van vaste breedte en het losgekoppelde CMS SignedData-object dat op deze pagina wordt beschreven. |
| Pro | Voegt PAdES B-T toe — een vertrouwde tijdstempel op de handtekeningwaarde — bovenop dezelfde structuur. |
| Enterprise | Voegt de langetermijnprofielen toe (B-LT, B-LTA): ingebed validatiemateriaal en documenttijdstempels, gelaagd op hetzelfde byte-range-fundament. |
Gerelateerde documentatie
Sectie met titel “Gerelateerde documentatie”- Incrementele updates en waarom ze ertoe doen — waarom toevoegen, in plaats van herschrijven, de byte range van de eerste handtekening intact houdt.
- PAdES baseline-profielen — wat er bovenop deze structuur wordt gelaagd en welk profiel voor een verplichting nodig is.
- Langetermijnvalidatie — hoe validatiebewijs wordt ingebed zodat een handtekening jarenlang verifieerbaar blijft.
Verklarende woordenlijst
Sectie met titel “Verklarende woordenlijst”- Signature dictionary — de PDF-dictionary die de signature handler, de
SubFilter, deByteRangeen deContents-waarde benoemt. ByteRange— een array van(offset, length)-integerparen die exact aangeven welke bytes de handtekeningdigest dekt.Contents— de hexadecimale entry die de handtekeningwaarde bevat (voor PDF 2.0 een losgekoppeld CMSSignedData-object); die is uitgesloten van haar eigen digest.- CMS
SignedData— Cryptographic Message Syntax (RFC 5652)-structuur die het certificaat van de ondertekenaar en de handtekeningbytes bevat. - PAdES — PDF Advanced Electronic Signatures: het ETSI-profiel voor CMS-handtekeningen in PDF, gedefinieerd in de ETSI EN 319 142-serie.
- Approval signature — een handtekening met
MDP-machtiging0; die de inhoud bevestigt zonder latere wijzigingen te beperken. - Certification signature — een handtekening met een DocMDP-machtiging (
MDP1–3) die beperkt welke latere wijzigingen het document conform houden.