제7장

C2PA 통합

콘텐츠 출처 및 진위성 연합 표준 구현

C2PA 소개

C2PA(Coalition for Content Provenance and Authenticity)는 Adobe, Microsoft, BBC 등이 주도하는 업계 표준으로, 디지털 콘텐츠의 출처와 편집 이력을 추적하는 개방형 기술 표준입니다.

ℹ️ C2PA의 핵심 개념
• 매니페스트: 콘텐츠에 대한 메타데이터와 어설션의 컨테이너
• 어설션: 콘텐츠에 대한 검증 가능한 주장
• 서명: 어설션을 암호화적으로 보호
• 하드 바인딩: 콘텐츠와 매니페스트의 불가분한 연결
• 신뢰 체인: 생성부터 현재까지의 완전한 이력

C2PA 아키텍처

C2PA는 계층화된 아키텍처를 사용합니다.

계층 구성요소 역할
자산 이미지, 비디오, 오디오, 문서 보호할 콘텐츠
매니페스트 메타데이터, 어설션, 서명 콘텐츠 이력 및 주장
매니페스트 스토어 매니페스트 컬렉션 편집 이력 관리
신뢰 앵커 인증서, 공개키 서명자 검증

C2PA SDK 설치 및 설정

C2PA JavaScript SDK를 사용하여 콘텐츠에 매니페스트를 추가합니다.

// C2PA SDK 설치
// npm install c2pa-node

import { createC2pa, SigningAlgorithm } from 'c2pa-node';
import { readFileSync, writeFileSync } from 'fs';

class C2PAManager {
    private c2pa: any;

    async initialize(): Promise {
        // C2PA 인스턴스 생성
        this.c2pa = createC2pa({
            // 사용자 에이전트 식별
            userAgent: 'WIA-AI-017/1.0'
        });

        console.log('✅ C2PA SDK 초기화 완료');
    }

    // 매니페스트 읽기
    async readManifest(filePath: string): Promise {
        const fileBuffer = readFileSync(filePath);

        const result = await this.c2pa.read({
            buffer: fileBuffer,
            mimeType: this.getMimeType(filePath)
        });

        if (result.manifestStore) {
            console.log('✅ 매니페스트 발견');
            return result.manifestStore;
        }

        console.log('⚠️ 매니페스트 없음');
        return null;
    }

    // MIME 타입 결정
    private getMimeType(filePath: string): string {
        const ext = filePath.split('.').pop()?.toLowerCase();
        const mimeTypes: Record = {
            'jpg': 'image/jpeg',
            'jpeg': 'image/jpeg',
            'png': 'image/png',
            'mp4': 'video/mp4',
            'mp3': 'audio/mpeg'
        };
        return mimeTypes[ext || ''] || 'application/octet-stream';
    }
}

// 사용 예제
const c2pa = new C2PAManager();
await c2pa.initialize();

const manifest = await c2pa.readManifest('photo.jpg');
if (manifest) {
    console.log('활성 매니페스트:', manifest.activeManifest);
}

매니페스트 생성 및 삽입

콘텐츠에 C2PA 매니페스트를 추가하여 출처와 편집 이력을 기록합니다.

// 매니페스트 생성 및 삽입
import { createC2pa } from 'c2pa-node';

class C2PAManifestCreator {
    // 매니페스트 정의 생성
    createManifestDefinition(
        assetInfo: {
            title: string;
            creator: string;
            aiGenerated: boolean;
            aiModel?: string;
        }
    ): any {
        const manifest = {
            // 클레임 생성자
            claim_generator: 'WIA-AI-017/1.0',

            // 제목
            title: assetInfo.title,

            // 어설션
            assertions: [
                // 크리에이티브 워크 어설션
                {
                    label: 'stds.schema-org.CreativeWork',
                    data: {
                        '@context': 'https://schema.org',
                        '@type': 'CreativeWork',
                        'author': [{
                            '@type': 'Person',
                            'name': assetInfo.creator
                        }]
                    }
                }
            ],

            // 재료 (부모 자산)
            ingredients: []
        };

        // AI 생성 어설션 추가
        if (assetInfo.aiGenerated) {
            manifest.assertions.push({
                label: 'c2pa.ai.generated',
                data: {
                    aiModel: assetInfo.aiModel || 'Unknown',
                    generatedAt: new Date().toISOString()
                }
            });
        }

        return manifest;
    }

    // 매니페스트를 자산에 삽입
    async embedManifest(
        inputPath: string,
        outputPath: string,
        manifestDef: any,
        signingKey: string,
        signingCert: string
    ): Promise {
        const c2pa = createC2pa();

        // 입력 파일 읽기
        const sourceBuffer = readFileSync(inputPath);

        // 서명 설정
        const signer = {
            algorithm: SigningAlgorithm.ES256,
            privateKey: readFileSync(signingKey),
            certificate: readFileSync(signingCert),
            timestampAuthority: {
                url: 'http://timestamp.digicert.com'
            }
        };

        // 매니페스트 삽입
        const result = await c2pa.sign({
            buffer: sourceBuffer,
            manifest: manifestDef,
            signer,
            options: {
                // 자산 하드 바인딩 생성
                reserve_size: 40960  // 40KB 예약
            }
        });

        // 서명된 자산 저장
        writeFileSync(outputPath, result.buffer);

        console.log('✅ C2PA 매니페스트 삽입 완료');
    }
}

// 사용 예제
const creator = new C2PAManifestCreator();

const manifestDef = creator.createManifestDefinition({
    title: 'AI로 생성한 풍경',
    creator: 'Jane Doe',
    aiGenerated: true,
    aiModel: 'DALL-E-3'
});

await creator.embedManifest(
    'original.jpg',
    'signed.jpg',
    manifestDef,
    'signing-key.pem',
    'signing-cert.pem'
);

어설션 타입

C2PA는 다양한 어설션 타입을 지원합니다.

// 일반적인 C2PA 어설션들
class C2PAAssertions {
    // 액션 어설션 (편집 작업)
    createActionAssertion(
        action: string,
        software: string,
        when: string
    ): any {
        return {
            label: 'c2pa.actions',
            data: {
                actions: [{
                    action,  // 'c2pa.edited', 'c2pa.filtered', etc.
                    softwareAgent: software,
                    when
                }]
            }
        };
    }

    // 데이터 해시 어설션
    createDataHashAssertion(
        hash: string,
        algorithm: string = 'sha256'
    ): any {
        return {
            label: 'c2pa.hash.data',
            data: {
                alg: algorithm,
                hash,
                name: 'jumbf manifest'
            }
        };
    }

    // 트레이닝 마이닝 어설션 (AI 훈련 동의)
    createTrainingMiningAssertion(
        allowed: boolean
    ): any {
        return {
            label: 'c2pa.training-mining',
            data: {
                entries: {
                    'c2pa.ai.training': allowed,
                    'c2pa.ai.inference': allowed,
                    'c2pa.data_mining': allowed
                }
            }
        };
    }

    // 위치 어설션
    createLocationAssertion(
        latitude: number,
        longitude: number
    ): any {
        return {
            label: 'stds.exif',
            data: {
                'exif:GPSLatitude': latitude,
                'exif:GPSLongitude': longitude
            }
        };
    }

    // 생성형 AI 어설션
    createGenerativeAIAssertion(
        modelName: string,
        version: string,
        prompt?: string
    ): any {
        return {
            label: 'c2pa.ai.generative',
            data: {
                model: modelName,
                version,
                prompt: prompt ? {
                    // 프롬프트는 해시로 저장 (개인정보 보호)
                    hash: 'sha256:' + prompt
                } : undefined
            }
        };
    }
}

// 사용 예제
const assertions = new C2PAAssertions();

// 편집 액션 추가
const editAssertion = assertions.createActionAssertion(
    'c2pa.color_adjustments',
    'Adobe Photoshop 2025',
    new Date().toISOString()
);

// AI 생성 표시
const aiAssertion = assertions.createGenerativeAIAssertion(
    'DALL-E-3',
    '1.0',
    'sha256_of_prompt'
);

// 훈련 동의 표시
const trainingAssertion = assertions.createTrainingMiningAssertion(false);

재료(Ingredients) 관리

부모 자산을 재료로 참조하여 편집 체인을 구축합니다.

// 재료 관리
class C2PAIngredientsManager {
    // 재료 추가 (부모 자산 참조)
    async addIngredient(
        parentAssetPath: string,
        relationship: 'parentOf' | 'componentOf'
    ): Promise {
        // 부모 자산의 매니페스트 읽기
        const c2pa = createC2pa();
        const parentBuffer = readFileSync(parentAssetPath);

        const parentManifest = await c2pa.read({
            buffer: parentBuffer,
            mimeType: this.getMimeType(parentAssetPath)
        });

        // 재료 정의 생성
        const ingredient = {
            title: 'Parent Asset',
            relationship,

            // 부모 자산의 해시 포함
            hash: parentManifest?.activeManifest?.claim_generator_info?.name || '',

            // 부모 매니페스트 참조
            manifest_data: parentManifest?.activeManifest
        };

        return ingredient;
    }

    // 합성 매니페스트 생성 (여러 재료)
    async createCompositeManifest(
        ingredients: string[],
        outputInfo: any
    ): Promise {
        const manifestDef = {
            claim_generator: 'WIA-AI-017/1.0',
            title: outputInfo.title,

            assertions: [
                // 합성 액션
                {
                    label: 'c2pa.actions',
                    data: {
                        actions: [{
                            action: 'c2pa.composed',
                            softwareAgent: outputInfo.software,
                            when: new Date().toISOString()
                        }]
                    }
                }
            ],

            // 모든 재료 추가
            ingredients: await Promise.all(
                ingredients.map(path =>
                    this.addIngredient(path, 'componentOf')
                )
            )
        };

        return manifestDef;
    }

    private getMimeType(filePath: string): string {
        const ext = filePath.split('.').pop()?.toLowerCase();
        const mimeTypes: Record = {
            'jpg': 'image/jpeg',
            'jpeg': 'image/jpeg',
            'png': 'image/png'
        };
        return mimeTypes[ext || ''] || 'image/jpeg';
    }
}

// 사용 예제
const ingredientsManager = new C2PAIngredientsManager();

// 여러 이미지를 합성한 경우
const compositeManifest = await ingredientsManager.createCompositeManifest(
    ['photo1.jpg', 'photo2.jpg', 'background.jpg'],
    {
        title: '합성 이미지',
        software: 'Adobe Photoshop 2025'
    }
);

매니페스트 검증

C2PA 매니페스트의 무결성과 신뢰 체인을 검증합니다.

// 매니페스트 검증
class C2PAValidator {
    async validate(filePath: string): Promise<{
        valid: boolean;
        errors: string[];
        warnings: string[];
        details: any;
    }> {
        const c2pa = createC2pa();
        const fileBuffer = readFileSync(filePath);

        const result = await c2pa.read({
            buffer: fileBuffer,
            mimeType: this.getMimeType(filePath)
        });

        const errors: string[] = [];
        const warnings: string[] = [];

        if (!result.manifestStore) {
            errors.push('매니페스트 없음');
            return { valid: false, errors, warnings, details: null };
        }

        const manifest = result.manifestStore.activeManifest;

        // 1. 서명 검증
        if (manifest.signature_info) {
            const sigValid = await this.verifySignature(
                manifest.signature_info
            );
            if (!sigValid) {
                errors.push('서명 검증 실패');
            }
        } else {
            errors.push('서명 없음');
        }

        // 2. 해시 무결성 검증
        const hashValid = await this.verifyAssetHash(
            fileBuffer,
            manifest
        );
        if (!hashValid) {
            errors.push('자산 해시 불일치 - 변조 가능성');
        }

        // 3. 타임스탬프 검증
        if (manifest.signature_info?.time) {
            const time = new Date(manifest.signature_info.time);
            if (time > new Date()) {
                errors.push('미래 타임스탬프 - 의심스러움');
            }
        }

        // 4. 인증서 체인 검증
        if (manifest.signature_info?.cert_chain) {
            const certValid = await this.verifyCertChain(
                manifest.signature_info.cert_chain
            );
            if (!certValid) {
                errors.push('인증서 체인 검증 실패');
            }
        }

        // 5. 재료 검증
        if (manifest.ingredients) {
            for (const ingredient of manifest.ingredients) {
                // 각 재료의 매니페스트도 검증
                // (재귀적 검증)
            }
        }

        return {
            valid: errors.length === 0,
            errors,
            warnings,
            details: {
                claimGenerator: manifest.claim_generator,
                signatureInfo: manifest.signature_info,
                assertionCount: manifest.assertions?.length || 0
            }
        };
    }

    private async verifySignature(signatureInfo: any): Promise {
        // 실제 구현에서는 암호화 라이브러리 사용
        // 서명을 공개키로 검증
        return true;  // 간소화
    }

    private async verifyAssetHash(
        buffer: Buffer,
        manifest: any
    ): Promise {
        // 자산의 실제 해시 계산
        const crypto = await import('crypto');
        const hash = crypto.createHash('sha256');
        hash.update(buffer);
        const actualHash = hash.digest('hex');

        // 매니페스트의 해시와 비교
        // (실제로는 더 복잡한 하드 바인딩 검증)
        return true;  // 간소화
    }

    private async verifyCertChain(certChain: any[]): Promise {
        // 인증서 체인 검증
        // 1. 각 인증서가 상위 인증서로 서명되었는지 확인
        // 2. 루트 CA가 신뢰할 수 있는지 확인
        // 3. 인증서가 유효 기간 내인지 확인
        return true;  // 간소화
    }

    private getMimeType(filePath: string): string {
        const ext = filePath.split('.').pop()?.toLowerCase();
        const mimeTypes: Record = {
            'jpg': 'image/jpeg',
            'jpeg': 'image/jpeg',
            'png': 'image/png'
        };
        return mimeTypes[ext || ''] || 'image/jpeg';
    }
}

// 사용 예제
const validator = new C2PAValidator();
const validation = await validator.validate('signed.jpg');

if (validation.valid) {
    console.log('✅ 매니페스트 유효');
} else {
    console.log('❌ 매니페스트 무효');
    console.log('오류:', validation.errors);
}

WIA-AI-017과 C2PA 통합

WIA-AI-017 표준을 C2PA 매니페스트에 통합합니다.

// WIA-AI-017 확장 어설션
class WIAC2PAIntegration {
    // WIA-AI-017 커스텀 어설션 생성
    createWIAAssertion(
        wiaRecord: any
    ): any {
        return {
            label: 'wia.ai.content-auth',
            data: {
                standardVersion: 'WIA-AI-017-v1.0',

                // 지문 정보
                fingerprints: wiaRecord.fingerprints,

                // 워터마크 정보
                watermarks: wiaRecord.watermarks,

                // 딥페이크 검출 결과
                deepfakeDetection: wiaRecord.deepfakeDetection,

                // WIA 출처 체인 URI
                provenanceURI: wiaRecord.provenanceURI
            }
        };
    }

    // C2PA + WIA 통합 매니페스트 생성
    async createIntegratedManifest(
        assetInfo: any,
        wiaData: any
    ): Promise {
        const manifest = {
            claim_generator: 'WIA-AI-017/1.0',
            title: assetInfo.title,

            assertions: [
                // 표준 C2PA 어설션들
                {
                    label: 'stds.schema-org.CreativeWork',
                    data: {
                        '@context': 'https://schema.org',
                        '@type': 'CreativeWork',
                        'author': [{
                            '@type': 'Person',
                            'name': assetInfo.creator
                        }]
                    }
                },

                // WIA 확장 어설션
                this.createWIAAssertion(wiaData)
            ],

            ingredients: []
        };

        return manifest;
    }
}

// 사용 예제
const integration = new WIAC2PAIntegration();

const wiaData = {
    fingerprints: [
        { algorithm: 'pHash', hash: 'abc123...', confidence: 0.95 }
    ],
    watermarks: [
        { algorithm: 'DCT', strength: 15, payload: 'WIA-AI-017' }
    ],
    deepfakeDetection: {
        isDeepfake: false,
        confidence: 0.98,
        methods: ['CNN', 'GAN-Fingerprint']
    },
    provenanceURI: 'ipfs://QmXxx...'
};

const integratedManifest = await integration.createIntegratedManifest(
    { title: 'My Photo', creator: 'Jane Doe' },
    wiaData
);

크로스 플랫폼 호환성

C2PA 매니페스트는 다양한 플랫폼과 도구에서 인식됩니다.

플랫폼/도구 C2PA 지원 기능
Adobe Photoshop ✅ 완전 생성, 편집, 검증
Content Credentials ✅ 완전 브라우저 검증
Microsoft Designer ✅ 완전 AI 생성 표시
Leica 카메라 ✅ 하드웨어 촬영 시 생성
BBC News ✅ 뉴스 진위성 검증
✅ C2PA 모범 사례
• 모든 콘텐츠에 매니페스트 추가
• 편집마다 새 매니페스트 생성
• 신뢰할 수 있는 CA의 인증서 사용
• 타임스탬프 서비스 활용
• 재료로 부모 자산 참조
• 정기적인 매니페스트 검증

📌 요약

📝 복습 문제

  1. C2PA의 주요 목적은 무엇입니까?
  2. 매니페스트와 어설션의 차이를 설명하십시오.
  3. 하드 바인딩이란 무엇이며 왜 중요합니까?
  4. 재료(Ingredients)를 사용하는 이유는 무엇입니까?
  5. C2PA 매니페스트를 검증할 때 확인하는 항목은 무엇입니까?
  6. WIA-AI-017을 C2PA와 통합하는 방법을 설명하십시오.
  7. C2PA를 지원하는 주요 플랫폼과 도구를 나열하십시오.
  8. C2PA 사용 시 모범 사례는 무엇입니까?

弘益人間 (홍익인간) · 널리 인간을 이롭게 하라