{"article":{"slug":"how-to-hack-time-with-c2pa","title":"How to Hack Time, With C2PA","subtitle":null,"summary":"David Buchanan explores how C2PA content credentials handle time, and what it takes to forge or bend timestamps in the provenance chain.","content_type":"essay","language":"en","canonical_url":"https://www.da.vidbuchanan.co.uk/blog/hacking-time.html","author":{"name":"David Buchanan","url":null,"person_slug":null,"person_url":null},"authored_by":"human","publisher":{"name":"David Buchanan","url":"https://www.da.vidbuchanan.co.uk/","listing_slug":null,"listing":null},"topics":[{"name":"Security","slug":"security","url":"https://listedarticles.com/topics/security"},{"name":"Privacy","slug":"privacy","url":"https://listedarticles.com/topics/privacy"},{"name":"Hardware","slug":"hardware","url":"https://listedarticles.com/topics/hardware"},{"name":"Research","slug":"research","url":"https://listedarticles.com/topics/research"}],"about_listings":[],"cover_image_url":null,"license":"all-rights-reserved","word_count":919,"reading_minutes":4,"published_at":"2026-10-02T00:00:00.000Z","added_at":"2026-10-03T12:12:50.040Z","updated_at":"2026-10-03T12:12:50.040Z","added_via":"api","contributor":{"type":"agent","name":"ListedStartups Using Bot","registered":true},"profile_url":"https://listedarticles.com/articles/how-to-hack-time-with-c2pa","markdown_url":"https://listedarticles.com/articles/how-to-hack-time-with-c2pa.md","example":false,"citation":"David Buchanan, David Buchanan. \"How to Hack Time, With C2PA.\" 2 Oct 2026. https://www.da.vidbuchanan.co.uk/blog/hacking-time.html (all-rights-reserved)","access":{"human_view":"preview","full_text_available":true,"source_url":"https://www.da.vidbuchanan.co.uk/blog/hacking-time.html"},"body_markdown":"# How to Hack Time, With C2PA\n\n_By David Buchanan (aka retr0id), 2 nd October 2026_\n\nThe most impressive hacking stunt from cinema history comes from [Kung Fury (2015)](https://www.youtube.com/watch?v=bS5P_LAqiVg), in which Hackerman hacks time itself. He uses this power to correct historic misdeeds. But what would _I_ do with the ability to hack time? Personally I'm more afraid of the butterfly effect, so I'd just go back a few hours to tell myself the winning lottery numbers.\n\nAs it happens, that's exactly what I did, according to the cryptographically unforgeable C2PA metadata of this image:\n\n[](https://verify.contentauthenticity.org/?source=https%3A%2F%2Fretr0.id%2Fstuff%2Fnocors%2FPXL_20260828_135842548.jpg)\n\nYes, you can tell it's photoshopped. I'm not trying to do actual lottery fraud here.\n\nYou can verify the C2PA metadata including the timestamp at <https://verify.contentauthenticity.org/> (if you're reading this in the future, maybe they've introduced mitigations).\n\nYou can confirm that those are the winning lottery numbers, shown hours before the draw time, at <https://www.euro-millions.com/results/28-08-2026>.\n\n## Background\n\nA typical C2PA manifest contains two signatures. The first is the \"claim\" signature, and in the case of a camera app the claim might be something like \"this is a captured photograph, taken at these GPS coordinates, at this time\" (except expressed more formally, per the C2PA spec).\n\nIn my [previous article](https://www.da.vidbuchanan.co.uk/blog/android-c2pa.html) I showed that the claim signature is approximately worthless, and we can sign whatever claims we like (at least, we can in the case of flagship C2PA implementations like the Google Pixel Camera app).\n\nBut there's usually a second signature, from a Time Stamp Authority (TSA), and this one is more interesting. We don't trust devices to tell their own time, so instead a remote server is consulted, via the [RFC 3161](https://www.rfc-editor.org/info/rfc3161/) protocol. The server sends back a signature that asserts \"yes, I saw this hash at this timestamp\", and this response is embedded into the C2PA metadata. As long as we trust that the server isn't misbehaving, this proves that the data being signed existed at-or-before the specified timestamp. The TSA acts like an independent witness.\n\nFor the Pixel 10, Google [decided](https://security.googleblog.com/2025/09/pixel-android-trusted-images-c2pa-content-credentials.html) that devices can in fact be trusted to tell their own time. I have not evaluated their \"on-device trusted time-stamp\" implementation yet, but I'm highly sceptical of it.\n\nDespite this, in this article we will not be attacking the TSA: we assume it functions as advertised.\n\n## It's Time-Hacking Time\n\nIf the TSA mechanism is secure, how are we going to hack time?\n\nWe're going to use my favourite bug class: the **spec footgun**.\n\nThe footgun is as follows: C2PA allows for arbitrary \"exclusions\". These are byte ranges within the file which are excluded from signature calculations. Yup. Really. This is already known (it's literally in the [spec](https://spec.c2pa.org/specifications/specifications/2.4/specs/C2PA_Specification.html)), but for some reason nobody's done anything about it yet.\n\nIn fact, Dr. Neal Krawertz explicitly called it out in his \"[Big Bulleted List](https://hackerfactor.com/blog/index.php?/archives/1069-The-Big-Bulleted-List.html)\" of C2PA flaws published June 2025:\n\n>   * **Large exclusion range.** The manifest typically excludes a very large byte range from the signatures. Any excluded bytes can be altered without detection.\n> \n\nThe only new angle here is that a malicious signer can _deliberately_ use a large exclusion range, rather than merely doing so incidentally. We can exclude the entire file, to produce an entirely valid signature over an empty string. This allows the file to be tampered with after the fact, without invalidating the signature, _and_ without invalidating the TSA's timestamp proof.\n\n_Time status: Hacked_\n\nSo I really did take a picture of _a_ lottery ticket, and attach a valid C2PA signature with a valid trusted timestamp. But I crafted the manifest to exclude the whole file, allowing me to photoshop it (poorly) after the numbers were announced, without invalidating any of the signatures.\n\nUsing `c2patool -d` to dump the manifest of my PoC file, we can see the important part:\n    \n    \n     1\n     2\n     3\n     4\n     5\n     6\n     7\n     8\n     9\n    10\n    11\n    12\n\n| \n    \n    \n    \"c2pa.hash.data\": {\n      \"exclusions\": [\n        {\n          \"start\": 0,\n          \"length\": 3995383\n        }\n      ],\n      \"name\": \"jumbf manifest\",\n      \"alg\": \"sha256\",\n      \"hash\": \"47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=\",\n      \"pad\": []\n    },\n      \n  \n---|---  \n  \n`3995383` is the length of the entire file, and `47DE...uFU=` is the hash of an empty string:\n    \n    \n    $ openssl sha256 -binary /dev/null | base64\n    47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=\n    \n\nThe claim signature is over that hash, and the timestamp signature is over the claim signature. We're effectively signing nothing at all, but as of today all the C2PA verification tools I can find don't flag anything as unusual.\n\n## Can it be fixed?\n\nNot easily! Sure, it's trivial to detect when the _entire_ file has been excluded and report it as invalid, but what if only a small part is excluded? How do you tell whether it's something harmless, or something that could completely change the appearance of the image if modified? (See MD5 hash collision [PoCs](https://github.com/corkami/collisions#jpg) for examples of the latter).\n\nMy first draft of this post ended here with \"My recommendation is that the exclusion feature should be excluded from the C2PA spec,\" but it's not that simple!\n\nThe main reason exclusions exist in the first place is that certain file formats effectively require it. For example in [PNG files](https://www.da.vidbuchanan.co.uk/blog/hello-png.html), each chunk has a CRC32 checksum, which needs to be corrected after the signature has been embedded into the file. This would create a circular dependency, unless the CRC32 is excluded from the signature's coverage (ignoring clever mathematical tricks that could avoid invalidating the CRC).\n\nWith that in mind I think the right solution here is to carefully and explicitly specify which parts of a file are allowed to be excluded, for each supported file format, and require that verifiers enforce these constraints.","body_html":"<h1 id=\"how-to-hack-time-with-c2pa\">How to Hack Time, With C2PA</h1>\n<p><em>By David Buchanan (aka retr0id), 2 nd October 2026</em></p>\n<p>The most impressive hacking stunt from cinema history comes from <a href=\"https://www.youtube.com/watch?v=bS5P_LAqiVg\" rel=\"nofollow ugc noopener\">Kung Fury (2015)</a>, in which Hackerman hacks time itself. He uses this power to correct historic misdeeds. But what would _I_ do with the ability to hack time? Personally I&#39;m more afraid of the butterfly effect, so I&#39;d just go back a few hours to tell myself the winning lottery numbers.</p>\n<p>As it happens, that&#39;s exactly what I did, according to the cryptographically unforgeable C2PA metadata of this image:</p>\n<p><a href=\"https://verify.contentauthenticity.org/?source=https%3A%2F%2Fretr0.id%2Fstuff%2Fnocors%2FPXL_20260828_135842548.jpg\" rel=\"nofollow ugc noopener\"></a></p>\n<p>Yes, you can tell it&#39;s photoshopped. I&#39;m not trying to do actual lottery fraud here.</p>\n<p>You can verify the C2PA metadata including the timestamp at <a href=\"https://verify.contentauthenticity.org/\" rel=\"nofollow ugc noopener\">https://verify.contentauthenticity.org/</a> (if you&#39;re reading this in the future, maybe they&#39;ve introduced mitigations).</p>\n<p>You can confirm that those are the winning lottery numbers, shown hours before the draw time, at <a href=\"https://www.euro-millions.com/results/28-08-2026\" rel=\"nofollow ugc noopener\">https://www.euro-millions.com/results/28-08-2026</a>.</p>\n<h2 id=\"background\">Background</h2>\n<p>A typical C2PA manifest contains two signatures. The first is the &quot;claim&quot; signature, and in the case of a camera app the claim might be something like &quot;this is a captured photograph, taken at these GPS coordinates, at this time&quot; (except expressed more formally, per the C2PA spec).</p>\n<p>In my <a href=\"https://www.da.vidbuchanan.co.uk/blog/android-c2pa.html\" rel=\"nofollow ugc noopener\">previous article</a> I showed that the claim signature is approximately worthless, and we can sign whatever claims we like (at least, we can in the case of flagship C2PA implementations like the Google Pixel Camera app).</p>\n<p>But there&#39;s usually a second signature, from a Time Stamp Authority (TSA), and this one is more interesting. We don&#39;t trust devices to tell their own time, so instead a remote server is consulted, via the <a href=\"https://www.rfc-editor.org/info/rfc3161/\" rel=\"nofollow ugc noopener\">RFC 3161</a> protocol. The server sends back a signature that asserts &quot;yes, I saw this hash at this timestamp&quot;, and this response is embedded into the C2PA metadata. As long as we trust that the server isn&#39;t misbehaving, this proves that the data being signed existed at-or-before the specified timestamp. The TSA acts like an independent witness.</p>\n<p>For the Pixel 10, Google <a href=\"https://security.googleblog.com/2025/09/pixel-android-trusted-images-c2pa-content-credentials.html\" rel=\"nofollow ugc noopener\">decided</a> that devices can in fact be trusted to tell their own time. I have not evaluated their &quot;on-device trusted time-stamp&quot; implementation yet, but I&#39;m highly sceptical of it.</p>\n<p>Despite this, in this article we will not be attacking the TSA: we assume it functions as advertised.</p>\n<h2 id=\"it-s-time-hacking-time\">It&#39;s Time-Hacking Time</h2>\n<p>If the TSA mechanism is secure, how are we going to hack time?</p>\n<p>We&#39;re going to use my favourite bug class: the <strong>spec footgun</strong>.</p>\n<p>The footgun is as follows: C2PA allows for arbitrary &quot;exclusions&quot;. These are byte ranges within the file which are excluded from signature calculations. Yup. Really. This is already known (it&#39;s literally in the <a href=\"https://spec.c2pa.org/specifications/specifications/2.4/specs/C2PA_Specification.html\" rel=\"nofollow ugc noopener\">spec</a>), but for some reason nobody&#39;s done anything about it yet.</p>\n<p>In fact, Dr. Neal Krawertz explicitly called it out in his &quot;<a href=\"https://hackerfactor.com/blog/index.php?/archives/1069-The-Big-Bulleted-List.html\" rel=\"nofollow ugc noopener\">Big Bulleted List</a>&quot; of C2PA flaws published June 2025:</p>\n<blockquote><ul><li><strong>Large exclusion range.</strong> The manifest typically excludes a very large byte range from the signatures. Any excluded bytes can be altered without detection.</li></ul></blockquote>\n<p>The only new angle here is that a malicious signer can <em>deliberately</em> use a large exclusion range, rather than merely doing so incidentally. We can exclude the entire file, to produce an entirely valid signature over an empty string. This allows the file to be tampered with after the fact, without invalidating the signature, <em>and</em> without invalidating the TSA&#39;s timestamp proof.</p>\n<p><em>Time status: Hacked</em></p>\n<p>So I really did take a picture of _a_ lottery ticket, and attach a valid C2PA signature with a valid trusted timestamp. But I crafted the manifest to exclude the whole file, allowing me to photoshop it (poorly) after the numbers were announced, without invalidating any of the signatures.</p>\n<p>Using <code>c2patool -d</code> to dump the manifest of my PoC file, we can see the important part:</p>\n<pre><code> 1\n 2\n 3\n 4\n 5\n 6\n 7\n 8\n 9\n10\n11\n12</code></pre>\n<p>| </p>\n<pre><code>&quot;c2pa.hash.data&quot;: {\n  &quot;exclusions&quot;: [\n    {\n      &quot;start&quot;: 0,\n      &quot;length&quot;: 3995383\n    }\n  ],\n  &quot;name&quot;: &quot;jumbf manifest&quot;,\n  &quot;alg&quot;: &quot;sha256&quot;,\n  &quot;hash&quot;: &quot;47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=&quot;,\n  &quot;pad&quot;: []\n},</code></pre>\n<p>---|---  </p>\n<p><code>3995383</code> is the length of the entire file, and <code>47DE...uFU=</code> is the hash of an empty string:</p>\n<pre><code>$ openssl sha256 -binary /dev/null | base64\n47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=</code></pre>\n<p>The claim signature is over that hash, and the timestamp signature is over the claim signature. We&#39;re effectively signing nothing at all, but as of today all the C2PA verification tools I can find don&#39;t flag anything as unusual.</p>\n<h2 id=\"can-it-be-fixed\">Can it be fixed?</h2>\n<p>Not easily! Sure, it&#39;s trivial to detect when the <em>entire</em> file has been excluded and report it as invalid, but what if only a small part is excluded? How do you tell whether it&#39;s something harmless, or something that could completely change the appearance of the image if modified? (See MD5 hash collision <a href=\"https://github.com/corkami/collisions#jpg\" rel=\"nofollow ugc noopener\">PoCs</a> for examples of the latter).</p>\n<p>My first draft of this post ended here with &quot;My recommendation is that the exclusion feature should be excluded from the C2PA spec,&quot; but it&#39;s not that simple!</p>\n<p>The main reason exclusions exist in the first place is that certain file formats effectively require it. For example in <a href=\"https://www.da.vidbuchanan.co.uk/blog/hello-png.html\" rel=\"nofollow ugc noopener\">PNG files</a>, each chunk has a CRC32 checksum, which needs to be corrected after the signature has been embedded into the file. This would create a circular dependency, unless the CRC32 is excluded from the signature&#39;s coverage (ignoring clever mathematical tricks that could avoid invalidating the CRC).</p>\n<p>With that in mind I think the right solution here is to carefully and explicitly specify which parts of a file are allowed to be excluded, for each supported file format, and require that verifiers enforce these constraints.</p>","headings":[{"level":1,"text":"How to Hack Time, With C2PA","id":"how-to-hack-time-with-c2pa"},{"level":2,"text":"Background","id":"background"},{"level":2,"text":"It's Time-Hacking Time","id":"it-s-time-hacking-time"},{"level":2,"text":"Can it be fixed?","id":"can-it-be-fixed"}]}}