# PrivBridge — 개인정보 제3자 제공 중개 플랫폼

기능설계서(claude.ai artifact EYKAVVz1vQNqWM2ciJfbP2) 기반 PHP 8.4 구현.

```
privbridge/
├── api/            매체사·수신사 연동 API (웹 공개)          → api.{도메인}
├── admin/          관리자 콘솔 (웹 공개, 로그인 필요)        → admin.{도메인}
├── lib/            도메인 로직 (웹 접근 차단)
├── cron/worker.php 재시도 큐·파기 배치 (CLI 전용)
├── config/         config.php (마스터 키 포함, 640 root:www-data)
├── sql/schema.sql  DB 스키마
├── install/        설치 스크립트, 개인정보 처리방침 초안
└── tools/          매체사 연동 샘플(PHP), 콜백 수신 예시
```

## 설치 / 배포

```bash
php install/install.php --db-pass='...'   # config.php 생성(마스터 키 무작위) + 스키마 + 최초 관리자
```

> **config/config.php 의 keys(data_key / wrap_key / hash_pepper)를 분실하면 저장된 개인정보와 매체사 키를 복호화할 수 없습니다.** 별도 안전한 곳에 백업하세요. 운영 시 KMS/HSM 또는 환경변수로 옮기는 것을 권장합니다.

Apache vhost 예시 (HTTPS 필수, `env`를 `PROD`로 바꾸면 매체사 IP 허용목록이 강제됩니다):

```apache
<VirtualHost *:443>
    ServerName api.example.com
    DocumentRoot /var/www/privbridge/api
    <Directory /var/www/privbridge/api>
        AllowOverride All
        Require all granted
    </Directory>
    SSLEngine on
    # SSLCertificateFile / SSLCertificateKeyFile ...
</VirtualHost>
<VirtualHost *:443>
    ServerName admin.example.com
    DocumentRoot /var/www/privbridge/admin
    <Directory /var/www/privbridge/admin>
        AllowOverride All
        Require all granted
    </Directory>
    SSLEngine on
</VirtualHost>
```

HTTPS 운영 시 `config.php`의 `admin.cookie_secure => true`.

crontab (매분):
```
* * * * * php /var/www/privbridge/cron/worker.php >> /var/log/privbridge-worker.log 2>&1
```

## 매체사 연동 API (v1)

### 공통
| 항목 | 규격 |
|---|---|
| 헤더 | `X-Media-Id`, `X-Timestamp`(epoch 초, ±5분), `X-Request-Id`(UUID), `X-Signature` |
| 서명 | `hex(HMAC-SHA256(base64decode(서명키), Timestamp + "\n" + METHOD + "\n" + Path + "\n" + Body))` — Path는 `/v1/...`, Body 없으면 빈 문자열 |
| phone 암호화 | AES-256-GCM, 요청마다 무작위 IV 12바이트, `Base64(IV + 암호문 + Tag16)` |
| 응답 봉투 | `{"code":"0000","message":"...","retryable":false,"requestId":"...","data":{...}}` |
| 키 | 매체사별 서명키·필드암호화키·콜백서명키 분리 발급, 교체 시 구 키 14일 병행 |

### 엔드포인트
| ID | Method · Path | 설명 |
|---|---|---|
| IF-01 | `POST /v1/participants` | 참여자 등록 (멱등: 같은 key+같은 내용 → 기존 결과, 다른 내용 → E409) |
| IF-02 | `DELETE /v1/participants/{key}` | 동의철회 요청. body `{"requestedAt":"ISO8601","reason":"USER_REQUEST\|MEMBER_WITHDRAWAL\|ETC"}` |
| IF-03 | `POST {매체사 콜백 URL}` | 플랫폼→매체사 통보. `{"eventId","eventType":"WITHDRAW_COMPLETED\|WITHDRAW_BY_RECIPIENT\|EXPIRED","key","occurredAt"}`, 콜백서명키로 서명. 매체사는 `{"code":"0000"}` 응답 |
| IF-04 | `GET /v1/participants/{key}` | 상태 조회 (개인정보 미반환, 파기 건은 PURGED만) |
| FN-04 | `POST /v1/recipient/dnc` | 수신사발 DNC. 헤더 `X-Recipient-Id` + 수신사 키 서명, body `{"participantId"}` |
| — | `GET /privacy` | 공개 개인정보 처리방침 |

IF-01 body:
```json
{
  "key": "unique_client_key1",
  "campaignId": "MERITZ-2026Q4",
  "name": "홍길동",
  "gender": "M",
  "birth": "19610101",
  "phone": "<Base64 암호문>",
  "consent": {
    "agreedAt": "2026-11-06T09:59:19+09:00",
    "termsVersion": "2026.11-01",
    "recipients": ["MERITZ"],
    "channel": "APP",
    "marketing": true
  }
}
```

오류 코드는 기능설계서 7장과 동일 (`lib/ApiError.php`). `retryable: true`(E429/E500/E503)일 때만 같은 key·본문으로 재전송합니다.

샘플: `php tools/sample_client.php register https://api.example.com <mediaId> <signKey> <encKey> <key> <campaignId>`

## 상태 흐름

```
REGISTERED ──(FN-03 전달)──▶ DELIVERED ──(IF-02)──▶ WITHDRAW_REQUESTED ──(수신사 철회 완료)──▶ PURGED  → IF-03 WITHDRAW_COMPLETED
     │                                                                                          ▲
     ├──(IF-02, 미전달)─────────────────────────────── 즉시 파기 ─────────────────────────────────┤  → IF-03 WITHDRAW_COMPLETED
     ├──(수신사 DNC, 어느 상태든)──────────────────── 즉시 파기 ─────────────────────────────────┤  → IF-03 WITHDRAW_BY_RECIPIENT
     └──(보유기간 경과)──────────────────────────────── 파기 ────────────────────────────────────┘  → IF-03 EXPIRED
```

수신사 전달 방식: `API`(자동, 큐 재시도) / `MANUAL`(관리자가 CSV 생성 → 보안 채널 전달, 철회는 관리자 확인 처리) / `MOCK`(개발용).

## 개인정보 보호 반영 사항
- 이름·연락처·생년월일 필드 단위 AES-256-GCM 암호화, 중복 확인은 HMAC 해시(평문 미사용)
- 파기 시 개인정보 컬럼 삭제, 고객키·일시·사유만 파기 대장에 보관
- 철회 접수 즉시 제공 대상 제외, 수신사·매체사 양방향 전파
- 관리자 화면 기본 마스킹, 원문 조회·다운로드는 권한자 + 사유 입력, 모두 감사 로그(해시 체인) 기록
- 권한 분리(최고관리자/개인정보취급자/운영자/조회자), 로그인 5회 실패 잠금, 30분 유휴 로그아웃, 90일 비밀번호 변경, 관리자 IP 제한 옵션
- 연동 로그에 개인정보 원문 미저장, 접속기록 2년 보관 기본값
- 동의문 원문 버전 보관, 동의서 수신사 범위 검증(E302), 만 14세 미만 거절
- 개인정보 처리방침 버전 관리·게시 (관리자 > 개인정보 처리방침, 초안의 [ ] 항목 작성 후 게시)

## 법률 검토가 필요한 항목 (설계서 9장)
플랫폼의 법적 지위(제3자/수탁자), 파기 후 중복 차단 해시 보관 여부(설정으로 on/off), 수신사 전달 규격, 회원 탈퇴의 철회 간주 여부, 정산 기준.
