Manipulatiedetectie in documentarchieven: hoe hashketens werken
Je kunt niet verifieren of een document is gemanipuleerd door er simpelweg naar te kijken. Een gewijzigde pdf ziet er identiek uit aan het origineel. Een vervangen bestand heeft dezelfde bestandsnaam. Een stilletjes bewerkte auditloogvermelding laat geen zichtbare sporen achter.
Manipulatiedetectie vereist een mechanisme dat wijziging een detecteerbaar artefact laat produceren, bij voorkeur een dat verifieerbaar is door iemand die er niet bij was bij de oorspronkelijke aanmaak. Hashketens zijn een van de kernmechanismen die dit bereiken.
Wat een cryptografische hash doet
Een cryptografische hashfunctie neemt een invoer van willekeurige lengte en produceert een uitvoer van vaste lengte (de digest) met twee kritieke eigenschappen:
- Determinisme: dezelfde invoer produceert altijd dezelfde uitvoer
- Lawinefect: elke wijziging in de invoer, zelfs een enkele bit, produceert een volledig andere uitvoer
SHA-256 produceert een 256-bits (32-bytes) digest. Voor elke praktische invoer is het computationeel onhaalbaar om:
- Twee verschillende invoeren te vinden die dezelfde uitvoer produceren (botsingsbestendigheid)
- De invoer te reconstrueren uit de uitvoer (preimage-bestendigheid)
- Een specifieke uitvoer te produceren zonder de invoer te kennen (tweede preimage-bestendigheid)
Deze eigenschappen betekenen dat een SHA-256-hash een betrouwbare vingerafdruk van een document is. Als de hash overeenkomt, is het document identiek aan wat werd gehasht. Als de hash niet overeenkomt, is er iets veranderd.
var hash = SHA256.HashData(File.ReadAllBytes("factuur.pdf"));
Console.WriteLine(Convert.ToHexString(hash));
// A3F8C2...1B7D (64 hex-tekens, altijd hetzelfde voor dit bestand, altijd anders als bestand wijzigt)
Waarom een enkele hash niet voldoende is
Een document hashen geeft je een vingerafdruk op een moment in de tijd. Maar wie slaat die vingerafdruk op, en wat verhindert dat iemand zowel het document als de opgeslagen hash vervangt?
Als je de hash opslaat in hetzelfde systeem als het document, en jij beheert dat systeem, kan een vastberaden aanvaller (of een interne actor die sporen uitwist) beide vervangen. De hash komt nog steeds overeen met het nieuwe document. De manipulatie is niet detecteerbaar.
Daarom vereist manipulatiedetectie een externe anker: een hash opgeslagen op een plaats die de bewaarder niet eenzijdig kan wijzigen. Een RFC 3161-tijdstempel is het standaardmechanisme: het embedt de documenthash in een token ondertekend door een externe TSA. De bewaarder kan het token niet wijzigen zonder de prive-sleutel van de TSA.
Wat een hashketen toevoegt
Een hashketen breidt dit uit naar een reeks gebeurtenissen. In plaats van alleen het document te hashen, hash je elke auditvermelding en neem je de hash van de vorige vermelding op in de huidige.
Hash(Vermelding N) = SHA256( GebeurtenisGegevens(N) + Hash(Vermelding N-1) )
Dit creëert een keten waarbij elke vermelding alle vorige vermeldingen vastlegt. Als je Vermelding 3 in een keten van 10 vermeldingen wijzigt, verandert Hash(Vermelding 3). Omdat Vermelding 4 Hash(Vermelding 3) bevat, verandert Hash(Vermelding 4). En zo door tot Vermelding 10.
Je kunt een eerdere vermelding niet stilletjes wijzigen. De keten valideert niet meer.
Hier is een minimale implementatie:
public sealed record AuditEntry(
string EventType,
string Actor,
DateTimeOffset Timestamp,
string DocumentHash,
string PreviousEntryHash)
{
public string ComputeHash()
{
var raw = $"{EventType}|{Actor}|{Timestamp:O}|{DocumentHash}|{PreviousEntryHash}";
return Convert.ToHexString(SHA256.HashData(Encoding.UTF8.GetBytes(raw)));
}
}
public static string AppendEntry(IList<AuditEntry> chain, string eventType,
string actor, byte[] documentBytes)
{
var previousHash = chain.Count == 0
? new string('0', 64)
: chain[^1].ComputeHash();
var docHash = Convert.ToHexString(SHA256.HashData(documentBytes));
var entry = new AuditEntry(eventType, actor, DateTimeOffset.UtcNow, docHash, previousHash);
chain.Add(entry);
return entry.ComputeHash();
}
public static bool VerifyChain(IList<AuditEntry> chain)
{
var expectedPrevious = new string('0', 64);
foreach (var entry in chain)
{
if (entry.PreviousEntryHash != expectedPrevious) return false;
expectedPrevious = entry.ComputeHash();
}
return true;
}
Verificatie is O(n) en vereist geen externe aanroepen. Iedereen met de keten kan VerifyChain uitvoeren en bevestigen dat geen vermelding is gewijzigd.
De keten verankeren met een tijdstempel
Een geverifieerde hashketen bewijst interne consistentie: geen vermelding werd gewijzigd nadat de keten was gebouwd. Maar het bewijst niet wanneer de keten werd gebouwd.
Als je vandaag een frauduleuze keten bouwt en beweert dat die gebeurtenissen van drie jaar geleden vertegenwoordigt, valideert de keten zelf correct. De hashketen heeft geen klok.
Hier verankeren RFC 3161-tijdstempels de keten aan de werkelijke tijd. Op sleutelmomenten in de keten (minimaal: wanneer de keten wordt afgerond; idealiter bij elke significante gebeurtenis) vraag je een tijdstempel aan bij een gekwalificeerde TSA. De TSA embedt de hash van de huidige vermelding in een ondertekend token met een betrouwbare tijdstempel.
Nu heeft de keten een externe anker. Je kunt de keten niet terugdateren voorbij de oudste tijdstempel, omdat het token van de TSA bewijst dat de hash op dat moment bestond, en de klok van de TSA is gezaghebbend.
// Na het toevoegen van een vermelding, vraag een tijdstempel aan voor de vermeldingshash
var entryHash = AppendEntry(chain, "aangemaakt", "api:tenant-1", documentBytes);
var entryHashBytes = Convert.FromHexString(entryHash);
var timestampToken = await RequestTimestampAsync(entryHashBytes, tsaUrl);
// Sla het token op naast de ketenkopie
chain[^1] = chain[^1] with { TimestampToken = timestampToken };
Een auditor die de keten verifieert, kan:
- Verifiëren dat de hashketen intern consistent is (geen wijzigingen)
- Elk tijdstempeltoken verifiëren aan de hand van het publieke certificaat van de TSA (geen terugdatering)
- De documenthash in elke vermelding verifiëren aan de hand van het huidige document (geen vervanging)
Hashketens versus andere integriteitsmecanismen
Append-only logs (bijv. databasetabellen zonder DELETE): Verhinderen verwijdering via toegangscontroles, maar zijn alleen zo betrouwbaar als het toegangscontrolesysteem. Een databasebeheerder kan ze omzeilen. Hashketens zijn wiskundig afgedwongen, niet beleidsmatig.
Digitale handtekeningen op afzonderlijke documenten: Een handtekening bewijst dat een document op een specifieke sleutel is ondertekend op het moment van ondertekening. Het legt niet vast wat er nadien met het document is gebeurd. Chain of custody vereist het vastleggen van gebeurtenissen na de ondertekening (transmissie, conversie, archivering), niet alleen het moment van ondertekening.
Blockchain: Gebruikt hashketens als kernprimitief, maar voegt gedistribueerde consensus toe om te voorkomen dat een enkele partij de geschiedenis herschrijft. Voor documentarchieven biedt een lokale hashketen verankerd door externe RFC 3161-tijdstempels dezelfde tamper-evidencegarantie zonder de complexiteit van een gedistribueerd netwerk. Het onderscheid is relevant: je hebt geen consensus tussen vreemden nodig om te bewijzen dat een document niet is gewijzigd; je hebt een extern anker nodig dat je niet zelf beheert.
WORM-opslag (Write Once Read Many): Verhindert wijziging op de opslaglaag. Het medium kan fysiek niet worden overschreven. Dit is complementair aan hashketens, geen vervanging. WORM bewijst dat de opgeslagen bits niet zijn veranderd. Een hashketen bewijst welke reeks gebeurtenissen die bits heeft geproduceerd. Beide samen bieden sterkere garanties dan elk afzonderlijk.
Hoe verificatie er in de praktijk uitziet
Een volledige verificatieworkflow voor een document in een hashgekoppeld archief:
- Haal het document en het auditspoor op uit het archief
- Bereken SHA-256 van het document; bevestig dat het overeenkomt met de
documentHashin de laatste ketenkopie - Loop de keten achteruit door, herbereken elke
entryHashen bevestig dat het overeenkomt met depreviousEntryHashin de volgende vermelding - Verifieer voor elke vermelding met een tijdstempeltoken het token aan de hand van het certificaat van de TSA en bevestig dat de hash in het token overeenkomt met de berekende
entryHash - Bevestig dat de documenthash in stap 2 overeenkomt met de documenthash in de vroegste vermelding die de definitieve vorm van het document dekt
Als alle vijf stappen slagen, is het document geverifieerd als ongewijzigd sinds aanmaak, legt het auditspoor nauwkeurig elke verwerkingsstap vast en bieden de tijdstempels extern verifieerbare tijdankers.
Deze verificatie vereist: het document, het auditspoor, de tijdstempeltokens en de publieke certificaten van de TSA. Geen netwerktoegang. Geen afhankelijkheid van de werking van de systemen van de bewaarder. Het bewijs is op zichzelf staand.
Manipulatiedetectie als infrastructuur
De meeste organisaties implementeren geen hashketens omdat het een geavanceerde functie lijkt. Dat is het niet. SHA-256 is beschikbaar in elke moderne runtime. De ketenconstructie is een paar dozijn regels code. Het voordeel is dat je documentintegriteit kunt bewijzen aan een sceptische derde partij zonder afhankelijk te zijn van hun vertrouwen in je organisatie.
Voor gereguleerde sectoren (financien, zorg, verzekeringen, overheid) is dat bewijs niet optioneel. Voor elke organisatie die digitale documenten als bewijs in een geschil wil presenteren, is het de basis.
SealDoc implementeert hashgekoppelde auditsporen voor elk document in zijn pijplijn. De keten wordt geexporteerd in het Legal Evidence Pack en is offline verifieerbaar. Voor organisaties die hun eigen bewijsinfrastructuur moeten bouwen, biedt de SealDoc API het auditspoor en tijdstempeltokens als gestructureerde gegevens die je kunt opnemen in je eigen verificatieworkflows.