Hoe tijd te hacken met C2PA

De meest indrukwekkende hack-stunt uit de filmgeschiedenis komt uit Kung Fury (2015), waarin Hackerman de tijd zelf hackt. Hij gebruikt deze kracht om historische onrechtmatigheden te herstellen. Maar wat zou ik doen met het vermogen om de tijd te hacken? Persoonlijk ben ik banger voor het vlindereffect, dus zou ik gewoon een paar uur teruggaan om mezelf de winnende lotterijgetallen te vertellen.

En dat is precies wat ik heb gedaan, volgens de cryptografisch onvervalsbare C2PA-metadata van deze afbeelding:

(Opmerking: het is duidelijk dat de afbeelding gefotshopt is; ik probeer hier geen echte loterijfraude te plegen).

Je kunt de C2PA-metadata, inclusief de tijdstempel, verifiëren via https://verify.contentauthenticity.org/ (als je dit in de toekomst leest, hebben ze wellicht inmiddels mitigaties geïmplementeerd). Je kunt bevestigen dat dit de winnende lotterijgetallen zijn, getoond uren voor het tijdstip van de trekking, via https://www.euro-millions.com/results/28-08-2026.

Achtergrond

Een typisch C2PA-manifest bevat twee handtekeningen. De eerste is de "claim"-handtekening. In het geval van een camera-app kan de claim iets zijn als: "dit is een vastgelegde foto, genomen op deze GPS-coördinaten, op dit tijdstip" (behalve dat dit formeler wordt geformuleerd, volgens de C2PA-specificatie).

In mijn vorige artikel liet ik zien dat de claim-handtekening nagenoeg waardeloos is; we kunnen elke claim tekenen die we willen (althans, dat kunnen we bij toonaangevende C2PA-implementaties zoals de Google Pixel Camera-app).

Er is echter meestal een tweede handtekening, afkomstig van een Time Stamp Authority (TSA), en deze is interessanter. We vertrouwen apparaten niet om hun eigen tijd door te geven. In plaats daarvan wordt een externe server geraadpleegd via het RFC 3161-protocol. De server stuurt een handtekening terug die bevestigt: "ja, ik heb deze hash op dit tijdstip gezien", en deze reactie wordt ingebed in de C2PA-metadata. Zolang we erop vertrouwen dat de server correct functioneert, bewijst dit dat de ondertekende gegevens bestonden op of vóór de opgegeven tijdstempel. De TSA fungeert als een onafhankelijke getuige.

Voor de Pixel 10 heeft Google besloten dat apparaten in feite wel kunnen worden vertrouwd om hun eigen tijd door te geven. Ik heb hun implementatie van "on-device trusted time-stamp" nog niet geëvalueerd, maar ik ben er zeer sceptisch over.

Ondanks dit zullen we in dit artikel de TSA niet aanvallen; we gaan ervan uit dat deze functioneert zoals geadverteerd.

Tijd hacken met tijd

Als het TSA-mechanisme veilig is, hoe gaan we dan de tijd hacken?

We maken gebruik van mijn favoriete klasse van bugs: de spec footgun (een fout in de specificatie die gebruikers zichzelf in de voet laten schieten).

De fout is als volgt: C2PA staat willekeurige "exclusions" (uitsluitingen) toe. Dit zijn byte-bereiken binnen het bestand die worden uitgesloten van de berekeningen van de handtekening. Ja, echt. Dit is al bekend (het staat letterlijk in de specificatie), maar om redenen die onbekend zijn, heeft er nog niemand iets aan gedaan.

Sterker nog, Dr. Neal Krawertz noemde dit expliciet in zijn "Big Bulleted List" van C2PA-gebreken, gepubliceerd in juni 2025:

Large exclusion range. The manifest typically excludes a very large byte range from the signatures. Any excluded bytes can be altered without detection.

De enige nieuwe invalshoek hier is dat een kwaadaardige ondertekenaar bewust een groot uitsluitingsbereik kan gebruiken, in plaats van dit slechts incidenteel te doen. We kunnen het hele bestand uitsluiten om een volledig geldige handtekening over een lege reeks tekens te produceren. Hierdoor kan het bestand achteraf worden aangepast zonder de handtekening of het tijdstempelbewijs van de TSA ongeldig te maken.

Tijdstatus: Gehackt

Ik heb dus echt een foto gemaakt van een loterijticket en daar een geldige C2PA-handtekening met een geldige vertrouwde tijdstempel aan gekoppeld. Maar ik heb het manifest zo samengesteld dat het hele bestand is uitgesloten, waardoor ik het (slecht) kon photoshoppen nadat de nummers bekend waren, zonder enige handtekening ongeldig te maken.

Door c2patool -d te gebruiken om het manifest van mijn PoC-bestand (Proof of Concept) te dumpen, zien we het belangrijkste deel:

"c2pa.hash.data": {
  "exclusions": [
    {
      "start": 0,
      "length": 3995383
    }
  ],
  "name": "jumbf manifest",
  "alg": "sha256",
  "hash": "47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=",
  "pad": []
},

3995383 is de lengte van het gehele bestand, en 47DE...uFU= is de hash van een lege reeks tekens:

$ openssl sha256 -binary /dev/null | base64
47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=

De claim-handtekening is gebaseerd op die hash, en de tijdstempel-handtekening is gebaseerd op de claim-handtekening. We tekenen effectief helemaal niets, maar tot op today vlaggen alle C2PA-verificatietools die ik kan vinden dit niet als ongebruikelijk.

Kan dit worden opgelost?

Niet gemakkelijk! Natuurlijk is het triviaal om te detecteren wanneer het hele bestand is uitgesloten en dit als ongeldig te rapporteren, maar wat als er slechts een klein deel is uitgesloten? Hoe bepaal je of dat onschadelijk is, of dat het iets is dat het uiterlijk van de afbeelding volledig kan veranderen als het wordt gewijzigd? (Zie PoC's van MD5-hash-collisies voor voorbeelden van dat laatste).

Mijn eerste concept van dit bericht eindigde met "Mijn aanbeveling is dat de uitsluitingsfunctie uit de C2PA-specificatie moet worden geschrapt", maar het is niet zo eenvoudig.

De belangrijkste reden dat uitsluitingen bestaan, is dat bepaalde bestandsformaten dit feitelijk vereisen. Bij PNG-bestanden heeft elk "chunk" bijvoorbeeld een CRC32-checksum, die moet worden gecorrigeerd nadat de handtekening in het bestand is geplaatst. Dit zou een cirkelvormige afhankelijkheid creëren, tenzij de CRC32 wordt uitgesloten van de dekking van de handtekening (gegeven dat er geen slimme wiskundige trucs zijn die het ongeldig maken van de CRC kunnen voorkomen).

Met dat in gedachten denk ik dat de juiste oplossing is om per ondersteund bestandsformaat zorgvuldig en expliciet vast te leggen welke delen van een bestand mogen worden uitgesloten, en te eisen dat verificatietools deze beperkingen handhaven.