콘텐츠 진위성과 무결성을 보장하는 암호화 기법
디지털 서명은 콘텐츠 인증의 핵심 기술입니다. 이는 공개 키 암호화(PKI)를 기반으로 하여 두 가지 핵심 보장을 제공합니다: 진위성(authenticity)과 무결성(integrity)입니다. 진위성은 콘텐츠가 주장된 생성자로부터 실제로 왔다는 것을 증명하고, 무결성은 서명 후 콘텐츠가 변경되지 않았음을 보장합니다.
디지털 서명의 작동 방식을 이해하기 위해서는 먼저 비대칭 암호화의 기본 개념을 파악해야 합니다.
비대칭 암호화는 수학적으로 연결되지만 서로 다른 두 개의 키를 사용합니다:
이 비대칭성은 강력한 보안 속성을 가능하게 합니다. 공개 키를 알고 있어도 개인 키를 계산하는 것은 컴퓨터적으로 불가능합니다.
// 키 쌍 생성 예제 (Ed25519 사용)
import { generateKeyPairSync } from 'crypto';
// Ed25519 키 쌍 생성
const { publicKey, privateKey } = generateKeyPairSync('ed25519', {
publicKeyEncoding: {
type: 'spki',
format: 'pem'
},
privateKeyEncoding: {
type: 'pkcs8',
format: 'pem'
}
});
console.log('공개 키:', publicKey);
console.log('개인 키:', privateKey);
디지털 서명은 전체 콘텐츠에 직접 서명하지 않습니다. 대신 암호화 해시 함수를 사용하여 콘텐츠의 고정 크기 지문을 생성합니다. 이 접근 방식은 효율성과 보안을 모두 제공합니다.
| 알고리즘 | 출력 크기 | 보안 상태 | 권장 사용 |
|---|---|---|---|
| MD5 | 128 비트 | ❌ 손상됨 | 사용 금지 |
| SHA-1 | 160 비트 | ⚠️ 약함 | 레거시만 |
| SHA-256 | 256 비트 | ✅ 안전함 | 범용 |
| SHA-512 | 512 비트 | ✅ 매우 안전함 | 높은 보안 |
| SHA-3 | 224-512 비트 | ✅ 최신 | 미래 보장 |
| BLAKE3 | 256 비트 | ✅ 고속 | 대용량 파일 |
// 다양한 해시 알고리즘으로 콘텐츠 해싱
import { createHash } from 'crypto';
import { readFileSync } from 'fs';
// 이미지 파일 읽기
const imageBuffer = readFileSync('photo.jpg');
// SHA-256 해시 계산
const sha256Hash = createHash('sha256')
.update(imageBuffer)
.digest('hex');
// SHA-512 해시 계산
const sha512Hash = createHash('sha512')
.update(imageBuffer)
.digest('hex');
// SHA-3 해시 계산
const sha3Hash = createHash('sha3-256')
.update(imageBuffer)
.digest('hex');
console.log('SHA-256:', sha256Hash);
console.log('SHA-512:', sha512Hash);
console.log('SHA-3:', sha3Hash);
여러 디지털 서명 알고리즘이 있으며 각각 고유한 강점과 트레이드오프가 있습니다. WIA-AI-017은 세 가지 주요 알고리즘군을 지원합니다.
RSA는 가장 오래되고 널리 배포된 디지털 서명 알고리즘입니다. 큰 소수의 곱셈은 쉽지만 인수분해는 어렵다는 수학적 어려움에 기반합니다.
// RSA 서명 생성 및 검증
import { createSign, createVerify } from 'crypto';
// 콘텐츠 해시 계산
const content = readFileSync('document.pdf');
const hash = createHash('sha256').update(content).digest();
// RSA 서명 생성
const sign = createSign('RSA-SHA256');
sign.update(hash);
const signature = sign.sign(privateKey, 'hex');
// 서명 검증
const verify = createVerify('RSA-SHA256');
verify.update(hash);
const isValid = verify.verify(publicKey, signature, 'hex');
console.log('서명 유효성:', isValid);
ECDSA는 타원 곡선 암호화를 사용하여 RSA보다 훨씬 작은 키로 동등한 보안을 제공합니다. 이는 대역폭과 저장 공간을 절약합니다.
// ECDSA 서명 (P-256 곡선 사용)
import { generateKeyPairSync, createSign, createVerify } from 'crypto';
// P-256 곡선으로 ECDSA 키 쌍 생성
const { publicKey, privateKey } = generateKeyPairSync('ec', {
namedCurve: 'P-256',
publicKeyEncoding: { type: 'spki', format: 'pem' },
privateKeyEncoding: { type: 'pkcs8', format: 'pem' }
});
// 콘텐츠에 서명
const content = '인증할 중요한 콘텐츠';
const sign = createSign('SHA256');
sign.update(content);
const signature = sign.sign(privateKey, 'base64');
// 서명 검증
const verify = createVerify('SHA256');
verify.update(content);
const isValid = verify.verify(publicKey, signature, 'base64');
console.log('ECDSA 서명 검증:', isValid);
Ed25519는 최신 서명 알고리즘으로 보안, 성능 및 단순성에 최적화되었습니다. 많은 일반적인 구현 실수에 대해 저항력이 있습니다.
// Ed25519 서명 - 권장 접근 방식
import { generateKeyPairSync, sign, verify } from 'crypto';
// Ed25519 키 쌍 생성
const { publicKey, privateKey } = generateKeyPairSync('ed25519');
// 콘텐츠 준비
const content = Buffer.from('인증할 이미지 데이터');
// 서명 생성 (해싱이 내부적으로 처리됨)
const signature = sign(null, content, privateKey);
// 서명 검증
const isValid = verify(null, content, publicKey, signature);
console.log('Ed25519 서명 검증:', isValid);
console.log('서명 크기:', signature.length, '바이트');
디지털 서명은 단지 암호화 바이트만이 아닙니다. 검증 가능성을 위해 풍부한 메타데이터와 함께 제공되어야 합니다.
// WIA-AI-017 서명 매니페스트 구조
interface SignatureManifest {
// 서명 식별
signatureId: string; // 고유 서명 ID (UUID)
version: string; // 매니페스트 버전 (예: "1.0")
// 콘텐츠 참조
contentHash: string; // SHA-256 콘텐츠 해시
contentType: string; // MIME 타입 (image/jpeg 등)
contentSize: number; // 바이트 단위 크기
// 암호화 정보
algorithm: string; // 서명 알고리즘 (Ed25519, ECDSA, RSA)
hashAlgorithm: string; // 해시 알고리즘 (SHA-256, SHA-512)
signature: string; // Base64 인코딩된 서명
publicKey: string; // Base64 인코딩된 공개 키
// 서명자 정보
signer: {
name: string; // 서명자 이름/조직
email?: string; // 연락 이메일
did?: string; // 분산 식별자 (선택)
};
// 타임스탬프
signedAt: string; // ISO 8601 타임스탬프
expiresAt?: string; // 서명 만료 (선택)
// 추가 컨텍스트
purpose?: string; // 서명 목적
chain?: SignatureManifest[]; // 이전 서명 체인
}
// 완전한 서명 매니페스트 생성
import { v4 as uuidv4 } from 'uuid';
import { generateKeyPairSync, sign, createHash } from 'crypto';
import { readFileSync } from 'fs';
class ContentSigner {
private privateKey: Buffer;
private publicKey: Buffer;
constructor() {
// 키 쌍 생성
const keys = generateKeyPairSync('ed25519');
this.privateKey = keys.privateKey;
this.publicKey = keys.publicKey;
}
signContent(filePath: string, signerName: string): SignatureManifest {
// 콘텐츠 읽기
const content = readFileSync(filePath);
// 콘텐츠 해시 계산
const contentHash = createHash('sha256')
.update(content)
.digest('base64');
// 서명 생성
const signature = sign(null, content, this.privateKey);
// 매니페스트 구성
const manifest: SignatureManifest = {
signatureId: uuidv4(),
version: '1.0',
contentHash: contentHash,
contentType: 'image/jpeg',
contentSize: content.length,
algorithm: 'Ed25519',
hashAlgorithm: 'SHA-256',
signature: signature.toString('base64'),
publicKey: this.publicKey.toString('base64'),
signer: {
name: signerName,
email: 'signer@example.com'
},
signedAt: new Date().toISOString(),
purpose: 'AI 콘텐츠 인증'
};
return manifest;
}
}
// 사용 예제
const signer = new ContentSigner();
const manifest = signer.signContent('photo.jpg', 'WIA 콘텐츠 연구소');
console.log('서명 매니페스트:', JSON.stringify(manifest, null, 2));
서명 생성보다 검증이 더 중요합니다. 검증 프로세스는 여러 단계를 거쳐 진위성과 무결성을 모두 확인해야 합니다.
// 서명 검증 구현
import { verify, createHash } from 'crypto';
import { readFileSync } from 'fs';
class SignatureVerifier {
verifyContent(filePath: string, manifest: SignatureManifest): boolean {
try {
// 1. 콘텐츠 읽기
const content = readFileSync(filePath);
// 2. 콘텐츠 해시 재계산
const calculatedHash = createHash('sha256')
.update(content)
.digest('base64');
// 3. 해시 무결성 검사
if (calculatedHash !== manifest.contentHash) {
console.error('❌ 콘텐츠 해시 불일치');
return false;
}
// 4. 만료 시간 검사
if (manifest.expiresAt) {
const expiry = new Date(manifest.expiresAt);
if (expiry < new Date()) {
console.error('❌ 서명 만료됨');
return false;
}
}
// 5. 공개 키 및 서명 디코딩
const publicKey = Buffer.from(manifest.publicKey, 'base64');
const signature = Buffer.from(manifest.signature, 'base64');
// 6. 암호화 서명 검증
const isValid = verify(null, content, publicKey, signature);
if (isValid) {
console.log('✅ 서명 검증 성공');
console.log('서명자:', manifest.signer.name);
console.log('서명 시각:', manifest.signedAt);
}
return isValid;
} catch (error) {
console.error('❌ 검증 오류:', error.message);
return false;
}
}
}
// 사용 예제
const verifier = new SignatureVerifier();
const manifest = JSON.parse(readFileSync('signature.json', 'utf-8'));
const isValid = verifier.verifyContent('photo.jpg', manifest);
신뢰할 수 있는 타임스탬프는 콘텐츠가 특정 시점에 존재했음을 증명합니다. 이는 날짜 조작을 방지하고 법적 비거부성을 제공합니다.
RFC 3161은 디지털 타임스탬프를 위한 표준 프로토콜을 정의합니다. TSA(Time Stamping Authority)는 콘텐츠 해시에 서명하고 정확한 시간을 인증합니다.
// RFC 3161 타임스탬프 요청
import axios from 'axios';
import { createHash } from 'crypto';
async function requestTimestamp(content: Buffer): Promise {
// 콘텐츠 해시 생성
const hash = createHash('sha256').update(content).digest();
// TSA 엔드포인트
const tsaUrl = 'https://freetsa.org/tsr';
// TSR (타임스탬프 요청) 생성
const tsrRequest = {
version: 1,
messageImprint: {
hashAlgorithm: 'SHA-256',
hashedMessage: hash.toString('base64')
},
nonce: Math.floor(Math.random() * 1000000),
certReq: true
};
// TSA에 요청 전송
const response = await axios.post(tsaUrl, tsrRequest, {
headers: { 'Content-Type': 'application/timestamp-query' },
responseType: 'arraybuffer'
});
// TST (타임스탬프 토큰) 반환
return Buffer.from(response.data).toString('base64');
}
// 사용 예제
const content = readFileSync('document.pdf');
const timestamp = await requestTimestamp(content);
console.log('타임스탬프 토큰:', timestamp);
암호화 키는 조직의 가장 중요한 자산입니다. 적절한 키 관리는 장기적인 보안에 필수적입니다.
정기적인 키 순환은 손상의 영향을 제한합니다. WIA-AI-017은 다음 순환 정책을 권장합니다:
// 키 순환 시스템
class KeyRotationManager {
private keyStore: Map = new Map();
private currentKeyId: string;
// 새 키 생성 및 활성화
rotateKeys(): string {
const newKeyId = `key-${Date.now()}`;
const { publicKey, privateKey } = generateKeyPairSync('ed25519');
// 새 키 저장
this.keyStore.set(newKeyId, { publicKey, privateKey });
// 이전 키는 검증용으로만 유지
const oldKeyId = this.currentKeyId;
if (oldKeyId) {
const oldKey = this.keyStore.get(oldKeyId);
// 이전 개인 키는 안전하게 보관만 (서명에는 사용 안함)
console.log(`이전 키 ${oldKeyId}는 검증 전용`);
}
// 현재 키 업데이트
this.currentKeyId = newKeyId;
console.log(`✅ 새 키 활성화: ${newKeyId}`);
return newKeyId;
}
// 서명에 현재 키 사용
sign(content: Buffer): { signature: string; keyId: string } {
const key = this.keyStore.get(this.currentKeyId);
const signature = sign(null, content, key.privateKey);
return {
signature: signature.toString('base64'),
keyId: this.currentKeyId
};
}
// 모든 키로 검증 (이전 서명 지원)
verify(content: Buffer, signature: string, keyId: string): boolean {
const key = this.keyStore.get(keyId);
if (!key) {
console.error('키를 찾을 수 없음:', keyId);
return false;
}
const sig = Buffer.from(signature, 'base64');
return verify(null, content, key.publicKey, sig);
}
}
중앙 집중식 PKI에만 의존하는 대신 WIA-AI-017은 분산 신뢰 모델을 지원합니다.
PGP/GPG 모델에서 사용자는 서로의 공개 키를 직접 검증하고 서명합니다. 신뢰는 소셜 그래프를 통해 전이됩니다.
DID는 블록체인 또는 분산 원장에 등록된 자체 주권적 디지털 신원을 제공합니다.
// DID 기반 서명자 정보
{
"signer": {
"did": "did:wia:0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb",
"didDocument": {
"@context": "https://www.w3.org/ns/did/v1",
"id": "did:wia:0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb",
"publicKey": [{
"id": "did:wia:...#key-1",
"type": "Ed25519VerificationKey2020",
"controller": "did:wia:0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb",
"publicKeyBase58": "H3C2AVvLMv6gmMNam3uVAjZpfkcJCwDwnZn6z3wXmqPV"
}]
}
}
}
양자 컴퓨터는 RSA 및 ECDSA를 포함한 현재 공개 키 암호화 시스템에 위협이 됩니다. WIA-AI-017은 양자 이후 암호화로의 전환을 준비합니다.
弘益人間 (홍익인간) · 널리 인간을 이롭게 하라