お断り: 本記事は C2PA Technical Specification v2.3(2026年4月時点)と c2pa-rs・c2patool のソースコードを筆者が読解して整理したものです。SDK のバージョンアップにより API や出力形式が変わることがあります。実装や設計判断の根拠として用いる際は、必ずC2PA 公式仕様および各 SDK の最新ドキュメントをご確認ください。本記事に誤りや古くなった箇所を見つけられた場合は、記事末尾のフィードバック枠よりお知らせいただけると助かります。
はじめに
C2PA における「バインディング(Binding)」とは、Manifest と対象デジタルアセットを暗号学的または視覚的に不可分に結びつける機構です。C2PA 仕様書では大きく Hard Bindings と Soft Bindings の 2 系統が定義されています。Manifest 必須の構成要素である Hard Binding はバイナリレベルでの完全な同一性を保証し、一方の Soft Binding は再エンコードやリサイズ等の不可逆変換を経た後でも対応する Manifest を再発見するための補完的な役割を果たします。
本記事は、シリーズ「C2PA 実装入門」 の応用編・ハードバインディング編です。後編にあたるソフトバインディング実装(Adobe Trustmark を使った透かし+c2pa.soft_binding assertion の付与)は ソフトバインディングを Adobe Trustmark で実装する をご覧ください。
Hard Binding と Soft Binding は何が違うか
両者の役割を一覧で並べておきます。
| 観点 | Hard Binding | Soft Binding |
|---|---|---|
| 目的 | バイナリ単位で Manifest と完全一致するコンテンツであることを保証 | コンテンツが変換・再エンコードされても Manifest を再発見できるようにする |
| 代表的な assertion ラベル | c2pa.hash.data / c2pa.hash.boxes / c2pa.hash.bmff | c2pa.soft_binding |
| アルゴリズム | SHA-256 / SHA-384 / SHA-512 などの暗号学的ハッシュ | 知覚ハッシュ、コンテンツフィンガープリント、電子透かしなど |
| 失敗時の意味 | コンテンツが Manifest と一致しない(改ざんまたは別ファイル) | 直接の対応は取れない(あるいは候補が見つからない) |
| 必須性 | Manifest に1つ必要(Section 11 参照) | 任意 |
ハードバインディングは「いま手元にあるバイト列が、この Manifest が署名した対象と完全に同一であるか」を判定します。1 ビットでも差異があればハッシュは一致しません。一方のソフトバインディングは、画像をリサイズしたり、動画を別コーデックで再エンコードしたりしても、コンテンツ固有の知覚的特徴量(透かしやフィンガープリント)から元の Manifest を検索・復元できるようにする仕組みです。
仕様書では soft binding を「the relationship between an asset and its manifest that allows the recovery of the manifest from a transformed version of the asset」と定義しています(Section 18)。
どちらをいつ使うか
実運用においては一方のみに依存するのではなく、配信経路や脅威モデルに応じて適切に組み合わせて運用します。
- 配信の前後でバイナリが改変されないユースケース(社内アーカイブ、原版管理、公的証拠の正本性確認など): Hard Binding 単体で十分な防御力を発揮
- SNS や CDN 経由で再エンコードされる前提のユースケース(Web メディア配信、報道写真、生成 AI コンテンツの流通など): Hard Binding に加えて Soft Binding を併用し、メタデータ剥離後も Manifest Repository への照会 によって来歴を復元可能にする
C2PA は Manifest をファイル内に直接埋め込む JUMBF 形式を基本としていますが、一般的な SNS や配信プラットフォームを経由する過程でメタデータが破棄される事例は少なくありません。Soft Binding と Manifest Repository(外部に Manifest を永続保存・公開する機構)を併用することで、メタデータが消失した配信アセットからでも正当な来歴情報を再取得できます。
ハードバインディングの実装
c2pa-rs および c2patool は、署名処理の実行時に Hard Binding Assertion を自動的に生成・注入します。開発者が明示的に記述しなくとも、SDK が対象ファイル形式に応じて最適なハッシュアルゴリズムや除外範囲を判定し、アセット全体のダイジェストを算出します(c2pa.hash.data / c2pa.hash.bmff / c2pa.hash.boxes の自動選択ロジックは公式実装を参照)。第5回で扱った EphemeralSigner を用いて、ハッシュ値がどのように組み込まれるかを実際に確認します。
プロジェクトのセットアップ
Rust および c2patool の環境下で、一時ディレクトリを作成して進めます。
cd /tmp
mkdir -p c2pa-hard-binding && cd c2pa-hard-binding
cargo init .
cargo add c2pa --features file_io
# サンプル画像をダウンロード(NASA アポロ17号撮影、パブリックドメイン)
curl -sL -o input.jpg https://raw.githubusercontent.com/contentauth/c2pa-rs/main/sdk/tests/fixtures/earth_apollo17.jpg
最小限の署名コード
第5回と同様、テスト用の証明書は EphemeralSigner が内部で自動生成するため、OpenSSL による事前の証明書作成は不要です。
// src/main.rs
use c2pa::{Builder, EphemeralSigner};
fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut builder = Builder::default()
.with_definition(r#"{
"title": "hard_binding_demo.jpg",
"claim_generator_info": [{
"name": "TechThanksHardBindingDemo",
"version": "0.1.0"
}],
"assertions": [{
"label": "c2pa.actions.v2",
"data": {
"actions": [{
"action": "c2pa.created",
"digitalSourceType": "http://cv.iptc.org/newscodes/digitalsourcetype/digitalCapture"
}]
},
"created": true
}]
}"#)?;
let signer = EphemeralSigner::new("C2PA Hard Binding Demo")?;
builder.sign_file(&signer, "input.jpg", "output.jpg")?;
println!("署名完了: output.jpg");
Ok(())
}
定義 JSON に記述した Assertion は c2pa.actions.v2 のみであり、ハッシュ関連の Assertion は含めていません。Hard Binding が SDK によってバックグラウンドで自動付与される挙動を確認することが本手順の目的です。
rm -f output.jpg
cargo run
# => 署名完了: output.jpg
自動付与されたハッシュを取り出す
c2patool を通常の引数で呼ぶと、c2pa.hash.* 系の内部 assertion は一覧には出てきません。詳細モード -d を付けると assertion_store が展開され、その中に c2pa.hash.data が確認できます。
c2patool -d output.jpg | jq '.manifests[].assertion_store["c2pa.hash.data"]'
実際の出力(再現環境での出力例):
{
"exclusions": [
{
"start": 9964,
"length": 13196
}
],
"name": "jumbf manifest",
"alg": "sha256",
"hash": "t//SV50JIargwLLt889e8iYhsH8n18VqhWoJ5UKHea0=",
"pad": "AAAAAAAAAAAA"
}
exclusions は「ハッシュ計算から除外するバイト範囲」を表し、Manifest 自身が格納される JUMBF コンテナ領域が指定されます。Manifest を含めてアセット全体のハッシュを算出すると循環参照(無限ループ)に陥るため、署名対象から自身の埋め込み領域をあらかじめ除外する設計となっています。この仕様は Section 18.5 “Data Hash” に準拠しています。この例では先頭 9,964 バイト目から 13,196 バイトにわたる JUMBF ボックスが除外対象です。
pad(および必要に応じた pad2)は、c2pa.hash.data Assertion 自体の CBOR バイナリ長を事前に確保した領域サイズに適合させるためのパディングです。署名前に Manifest 全体の配置サイズが固定されているため、ハッシュ値の長さに応じた全体サイズの変動を防ぐ目的で付与されます。
検証時には、この exclusions を除外したバイナリ列に対して指定のハッシュアルゴリズム(本例では SHA-256)を再計算し、Manifest 内の hash との一致を検証します。1 バイトでも改変があれば検証は失敗します。
ちなみに、入力画像とハッシュアルゴリズム(hash_alg、既定は sha256)が同じであれば、署名を何度やり直しても hash と start は同じ値になります。ハッシュ対象は「exclusions を除いたバイト列」、つまり元の JPEG の画像データ部分そのものなので、入力が変わらなければ結果も変わらない、というカラクリです。署名鍵・証明書・タイムスタンプ・Claim UUID はすべて Manifest JUMBF 内 = exclusions の中に閉じているので、EphemeralSigner が毎回違う鍵を生成してもハッシュ値には影響しない設計になっています。start も JPEG 内の Manifest JUMBF ボックスの挿入位置(APP11 マーカー周辺)が構造的に決まるので同じ位置です。一方の length は Manifest 内のタイムスタンプや Ephemeral 証明書の情報によって CBOR のバイト長が前後することがあり、署名ごとに数バイト揺れる値です。hash_alg を sha256 から sha384 などに切り替えれば、当然 hash の値(長さと内容)は別物になります。
検証結果でハードバインディングの成立を確認する
c2patool の validation_results を見ると、ハッシュ検証が通ったことが success 側にきっちり記録されています。
c2patool output.jpg | jq '.validation_results.activeManifest.success[] | select(.code | startswith("assertion."))'
出力:
{
"code": "assertion.hashedURI.match",
"url": "self#jumbf=/c2pa/urn:c2pa:.../c2pa.assertions/c2pa.actions.v2",
"explanation": "hashed uri matched: self#jumbf=c2pa.assertions/c2pa.actions.v2"
}
{
"code": "assertion.hashedURI.match",
"url": "self#jumbf=/c2pa/urn:c2pa:.../c2pa.assertions/c2pa.hash.data",
"explanation": "hashed uri matched: self#jumbf=c2pa.assertions/c2pa.hash.data"
}
{
"code": "assertion.dataHash.match",
"url": "self#jumbf=/c2pa/urn:c2pa:.../c2pa.assertions/c2pa.hash.data",
"explanation": "data hash valid"
}
assertion.dataHash.match が「data hash valid」を返していれば、exclusions を除外したバイナリ列の SHA-256 が Manifest 内の hash と完全に一致したことを示します。
検証として、署名済み output.jpg を複製し、exclusions の除外範囲外(本例では 9,964〜23,159 バイト目以外の実データ領域)を 1 バイトだけ改変して再検証を実行します。
cp output.jpg tampered.jpg
printf '\xFF' | dd of=tampered.jpg bs=1 seek=50000 count=1 conv=notrunc
c2patool tampered.jpg | jq '.validation_status'
出力:
[
{
"code": "signingCredential.untrusted",
"url": "self#jumbf=/c2pa/urn:c2pa:.../c2pa.signature",
"explanation": "signing certificate untrusted"
},
{
"code": "assertion.dataHash.mismatch",
"url": "self#jumbf=/c2pa/urn:c2pa:.../c2pa.assertions/c2pa.hash.data",
"explanation": "asset hash error, name: jumbf manifest, error: hash verification( Hashes do not match )"
}
]
先ほどまで success 側にあった assertion.dataHash.match が消え、代わりに assertion.dataHash.mismatch がエラーとして記録されました。わずか 1 バイトのビット反転であっても改ざんとして即座に検出できる点が、Hard Binding の暗号学的な堅牢性です(なお、signingCredential.untrusted はテスト用自己署名証明書に起因する未信頼警告であり、ハッシュ検証の結果とは独立しています)。
まとめ
Hard Binding は C2PA Manifest における必須要素であり、c2pa-rs の Builder を利用することで低レイヤーの処理が自動化されるため、開発者側での追加実装コストは軽微です。内部的には、JUMBF 埋め込み領域を exclusions として定義し、残余のバイナリ全体をハッシュ化して c2pa.hash.data Assertion を固定長でパディング生成する精密なシーケンスが実行されています。この仕組みはバイナリ完全一致を前提とするため、配信経路でリサイズや再圧縮が適用されると必然的に検証失敗となります。
不可逆な変換を経てもなお Manifest との紐付けを維持するための補完技術が Soft Binding です。続く後編 ソフトバインディングを Adobe Trustmark で実装する では、C2PA 公認レジストリに登録された Adobe Trustmark を用い、画像へ知覚的透かしを埋め込みつつ c2pa.soft_binding Assertion として Manifest に記録する実践的な実装を解説します。
TechThanks では、生成 AI 由来コンテンツやメディア配信における C2PA 導入の支援を行っています。Hard / Soft Binding の組み合わせ設計、Manifest Repository の構築、検証パイプラインの実装まで含めた相談がありましたら、お気軽にお問い合わせください。