C2PAを利用して時間をハッキングする方法
C2PA証明は画像の出所と作成時期を証明することを目的としている。David Buchananは、証明に写真の撮影時期とは全く関係のないタイムスタンプを載せる方法を示し、この形式が修正可能かどうかを問う。
日本語
コピー

Welcome to my
::'########::'##::::::::'#######:::'######:::
:: ##.... ##: ##:::::::'##.... ##:'##... ##::
:: ##:::: ##: ##::::::: ##:::: ##: ##:::..:::
:: ########:: ##::::::: ##:::: ##: ##::'####:
:: ##.... ##: ##::::::: ##:::: ##: ##::: ##::
:: ##:::: ##: ##::::::: ##:::: ##: ##::: ##::
:: ########:: ########:. #######::. ######:::
::........:::........:::.......::::......::::
Blog Index
著者:David Buchanan(別名 retr0id)、2026年10月2日

映画史上もっともハッタリの効いたハッキング描写は Kung Fury(2015) のものだ。作中の Hackerman は時間そのものをハックし、その力で歴史上の悪行を正していく。だが私が時間をハックする力を手に入れたら何をするか? 私はどちらかというとバタフライ効果を恐れる質なので、数時間だけ過去に戻って、当選する宝くじの番号を自分に教えるくらいだろう。
偶然なことに、これはまさに私がやったことだ。証拠がこの画像に埋め込まれた、暗号学的に偽造不可能な C2PA メタデータである:
そう、見るからに合成だと分かる。本気で宝くじ詐欺を働くつもりはない。
https://verify.contentauthenticity.org/ でこの C2PA メタデータを検証できる。タイムスタンプも含めて(未来にこれを読んでいるなら、その頃には対策が進んでいるかもしれない)。
https://www.euro-millions.com/results/28-08-2026. では、これが実際の当選番号であること、そして抽選時刻の数時間前に表示されていたことを確認できる。
背景
典型的な C2PA manifest には署名が2つある。1つ目は「claim」署名。カメラアプリを例にすると、claim の内容はおおよそ「これは撮影された写真で、撮影日時と GPS 座標はこれこれ」というものだ(もちろん C2PA 仕様にはもっと正式な表現がある)。
前回の記事で書いたとおり、claim 署名はほぼ無価値で、好きな claim にいくらでも署名できる(少なくとも Google Pixel Camera App のようなフラッグシップ級の C2PA 実装では)。
だが通常は2つ目の署名があり、こちらはずっと面白い。タイムスタンプ局(TSA)によるものだ。デバイスが自己申告する時刻は信用せず、代わりに RFC 3161 プロトコルでリモートサーバーに問い合わせる。サーバーは「確かにこのタイムスタンプでこのハッシュを見た」と署名して返し、その応答が C2PA メタデータに埋め込まれる。サーバーが不正をしていないと信じる限り、署名対象のデータがそのタイムスタンプ以前(または同時点)に存在したことの証明になる。TSA は独立した証人の役割を担う。
Pixel 10 について、Google はデバイス自身の時刻申告を信頼してよいと判断した。彼らの「デバイス上の信頼できるタイムスタンプ」実装はまだ評価していないが、かなり疑わしいと思っている。
とはいえ本記事は TSA を攻撃しない。宣伝どおりに動作していると仮定する。
時間をハックする
TSA の仕組みが安全だとして、どうやって時間をハックするのか?
ここで使うのは私が最も好きな種類の脆弱性、仕様自身の自滅である。
自滅の中身はこうだ。C2PA は任意の「除外範囲」を認めている。ファイル内で署名計算から除外されるバイト範囲のことだ。本当に。マジで。仕様にそう書いてあり、なぜか今まで誰も問題視してこなかった。
実際、Neal Krawertz 博士が2025年6月に公表した C2PA の欠陥「大リスト」でも、この点は明確に指摘されている:
- 除外範囲が大きすぎる。 manifest は非常に大きなバイト範囲を署名対象から除外するのが通例で、除外されたバイトはどれも検出されずに改変できる。
ここでの新味は、悪意ある署名者が大規模な除外を意図的に使えるという点だけだ。うっかりそうなるのではなく。ファイル全体を除外すれば、空文字列に対して完全に有効な署名を生成できる。こうすれば、ファイルを後から改ざんしても署名は有効なままで、しかも TSA のタイムスタンプ証明も有効なままになる。
時間の状態:ハック済み
というわけで、私は実際に宝くじの写真を撮り、有効な C2PA 署名と有効な信頼できるタイムスタンプを付けた。ただし manifest を構築する際にファイル全体を除外したので、番号が発表された後に(下手くそに)合成しても、どの署名も壊れない。
c2patool -d で PoC ファイルの manifest を出力すると、要点が見える:
"c2pa.hash.data": { "exclusions": [ { "start": 0, "length": 3995383 } ], "name": "jumbf manifest", "alg": "sha256", "hash": "47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=", "pad": []},
3995383 はファイル全体の長さ、47DE...uFU= は空文字列のハッシュだ:
$ openssl sha256 -binary /dev/null | base64
47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=
claim 署名はこのハッシュに対して行われ、timestamp 署名は claim 署名に対して行われる。要するに、署名しているのは実質的に何もない。それなのに今日に至るまで、私が試せる限りの C2PA 検証ツールはどれも、この状況を異常として報告しなかった。
修正できるのか?
そう簡単ではない。ファイル全体が除外されているのを検出して無効と報告するのは確かに簡単だ。だがごく一部だけが除外されていたら? それが無害なのか、改変されれば画像の見た目を根本から変えうるものなのか(後者の例は MD5 衝突 PoC を見てほしい)、どうやって判断するのか?
この記事の初稿はここで終わっていて、「私の提案は exclusion 機能を C2PA 仕様から排除することだ」と書いてあった。だが話はそう単純ではない!
exclusion が存在する主な理由は、一部のファイル形式が本当にそれを必要としているからだ。たとえば PNG ファイルでは、各 chunk が CRC32 チェックサムを持っており、ファイルに署名を埋め込んだ後はこのチェックサムを再計算しなければならない。CRC32 を署名の対象範囲から除外しない限り、ここで循環依存が生じる(CRC を無効化せずに済む巧妙な数学的手法はここでは措く)。
だから正しいやり方はこうだと思う。対応するファイル形式ごとに、ファイルのどの部分を除外してよいかを注意深く明示的に定め、検証器にその制約を強制させる。
特に断りのない限り、このブログの内容はすべて thinking meat が生成したものだ。
