Skip to content

에이전트 전용 봉인 모듈(agent_seal): 완전 엔트로피 기계키 봉인 + 정보이론적 OTP - #4845

Closed
kevin9327 wants to merge 2 commits into
edwardkim:develfrom
kevin9327:gym_agent_seal
Closed

에이전트 전용 봉인 모듈(agent_seal): 완전 엔트로피 기계키 봉인 + 정보이론적 OTP#4845
kevin9327 wants to merge 2 commits into
edwardkim:develfrom
kevin9327:gym_agent_seal

Conversation

@kevin9327

Copy link
Copy Markdown
Contributor

Closes #4843

무엇을 / 왜

문서 암호화의 약한 고리는 알고리즘이 아니라 사람이 고른 낮은 엔트로피 암호입니다.
Argon2id 같은 강한 KDF 도 이 사실을 늦출 뿐 없애지 못합니다. 반면 에이전트는 완전
엔트로피 기계 키
(OS 난수 32바이트)를 직접 보관·전달할 수 있으므로 이 약한 고리를
아예 제거할 수 있습니다.

그리고 "양자내성보다 더 강한" 암호는 존재하지 않습니다 — 딱 하나 예외가 정보이론적
안전성(일회용 패드)
입니다. 이 PR 은 이 두 가지를 정직하게 제공하는 새 독립 모듈
src/agent_seal.rs 를 추가합니다. 마법의 "양자 초월 암호"는 없습니다.

동시에 작업 중인 src/security_trailer.rs(ML-KEM 공개키 축)와는 완전히 독립입니다.
자체 마법 마커(RHWPAGT1/RHWPAGND)를 써서 형식이 서로 겹치지 않습니다. 그 파일은
이 PR 에서 건드리지 않았습니다.

두 가지 모드

1번 모드 — 완전 엔트로피 에이전트 키 (계산적 안전, 양자내성)

  • agent_keygen() -> [u8; 32] — OS 난수 32바이트. 이 원시 키 자체가 "에이전트 암호"
    입니다(사람 암호·Argon2id 없음). 호출자가 base64/hex 로 인코딩해 다룹니다.
  • seal_with_key(host, secret, key) -> Vec<u8> — XChaCha20-Poly1305 AEAD, 매 호출 새
    24바이트 난수 논스. 트레일러 헤더를 AAD 로 묶어 변조를 탐지합니다. 트레일러는 호스트
    문서 뒤에 붙어 평범한 뷰어가 무시합니다(가시 계층 = 호스트). 인증(AEAD) 있음.
  • open_with_key(bytes, key) -> Opened — 꼬리에서 트레일러를 탐지해
    Plain / Sealed{plaintext} / Broken{reason} 를 반환. 손상된 입력에도 패닉하지 않음
    (엄격한 경계 검사). 키 불일치·변조는 AEAD 인증 실패로 Broken.

2번 모드 — 일회용 패드 (정보이론적 안전 — 유일하게 "양자보다 강한" 것)

  • otp_generate_pad(len) -> Vec<u8>len 바이트 진짜 난수.
  • otp_seal(secret, pad) -> Result<Vec<u8>, AgentSealError>pad.len() >= secret.len()
    강제(짧으면 PadTooShort 오류; 잘린 봉인을 만들지 않음) → secret XOR pad.
    1번과 같은 트레일러 컨테이너에 distinct algo 바이트로 자기서술합니다.
  • otp_open(sealed, pad) -> Opened — XOR 로 복원. 엄격한 경계·무패닉.

OTP 의 정직한 조건과 한계 (코드 doc 에도 기재)

OTP 가 무조건적으로 안전한 것은 오직 다음이 모두 성립할 때뿐입니다:

  1. 패드가 진짜 난수여야 한다(의사난수 PRNG 는 안 됨).
  2. 패드 길이가 메시지 길이 이상이어야 한다.
  3. 패드를 정확히 한 번만 써야 한다(재사용 시 완전히 깨짐).
  4. 패드를 대역 외(out-of-band)로 안전하게 공유해야 한다.

이 조건을 못 지키면 OTP 는 오히려 약한 XOR 암호로 전락합니다. 또한 OTP 는 기밀성만
제공
하며 무결성/인증이 없어 비트 뒤집기 변조를 탐지하지 못합니다(1번 모드의 AEAD 와
대비되는 정직한 한계). 인증이 필요하면 1번 모드를 쓰십시오.

트레일러 형식 (자기서술)

  MAGIC_START "RHWPAGT1"  (8)
  version                 (1)  = 1
  algo                    (1)  = 1(XChaCha20-Poly1305) | 2(OTP-XOR)
  nonce_len               (1)  = 24(XChaCha) | 0(OTP)
  nonce                   (nonce_len)   ── 위까지가 header = AEAD 의 AAD ──
  ciphertext              (가변)
  trailer_len  u64 LE     (8)
  MAGIC_END "RHWPAGND"    (8)

의존성

  • chacha20poly1305 = { version = "0.10", features = ["alloc"] } 추가(순수 Rust, wasm 호환).
  • OTP 모드는 XOR + 기존 getrandom 만 사용(추가 의존성 없음).

검증

  • cargo build --bin rhwp 통과.
  • cargo test --lib agent_seal::16/16 통과:
    • 1번: keygen 32바이트·난수성, seal→open 라운드트립(비밀 복원 + 가시 계층=호스트),
      빈 비밀 라운드트립, 오키 → Broken(무패닉), 서로 다른 두 무작위 키의 상호 열람 불가,
      변조 암호문 → Broken(AEAD), 평문 호스트 → Plain, 손상 입력 전수 무패닉.
    • 2번: OTP 라운드트립(길이 정확/여유 패드), 짧은 패드 → PadTooShort 오류(잘린 봉인 아님),
      복원 시 짧은 패드 → Broken, ciphertext == secret XOR pad 직접 검증, 손상 입력 무패닉.
    • 교차 모드: OTP 블롭을 open_with_key 로 / AEAD 블롭을 otp_open 으로 열면 Broken.
  • cargo test --lib — 전체 3718 passed, 0 failed, 13 ignored (회귀 없음).
  • 리프 파일만 rustfmt --edition 2021 src/agent_seal.rs 로 정렬(--check 통과). cargo clippy --lib 통과.

사람 암호(약한 고리) 없이 에이전트가 보관하는 완전 엔트로피 기계키로
문서를 봉인하는 모듈을, security_trailer 와 독립된 자체 형식으로 추가한다.

- 1번 모드(계산적 안전·양자내성): XChaCha20-Poly1305 AEAD, KDF 없음,
  24바이트 난수 논스, 헤더 AAD, 호스트 뒤 트레일러(가시 계층=호스트).
- 2번 모드(정보이론적 안전): 일회용 패드 XOR. 패드 길이>=메시지 강제,
  같은 컨테이너에 distinct algo. 기밀성만 제공(인증 없음) — 정직한 한계 명시.
- 자체 마법 RHWPAGT1/RHWPAGND, 꼬리 탐지, 엄격한 경계로 손상 입력 무패닉.
- chacha20poly1305 0.10(alloc) 의존성 추가. OTP 는 XOR+getrandom 만 사용.
- 단위 시험 16종(라운드트립·오키/변조 Broken·교차모드 거부·무패닉) 통과.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

CI 가 WASM32 경로 3곳에서 실패했다 (PR edwardkim#4845): Lint 의 Clippy (WASM32),
Frontend package gates 의 wasm-pack 빌드, Canvas visual diff 의 wasm 빌드.

원인은 edwardkim#4826·edwardkim#4838 과 같은 **getrandom 0.2** 다. chacha20poly1305 의
기본 `getrandom` 피처가 rand_core 0.6 을 거쳐 0.2 를 끌어오는데, 0.2 는
wasm32-unknown-unknown 에서 `js` 피처 없이 compile_error! 로 죽는다.
이 저장소가 켜 둔 getrandom 0.4 + wasm_js 는 버전이 달라 그 자리를 못 덮는다.

agent_seal 은 기계키·논스·OTP 패드를 전부 `getrandom::fill`(0.4)로 직접
만들므로 그 피처가 애초에 필요 없다. default-features 를 끄고 `alloc` 만
남긴다 — 암호 알고리즘·포맷·동작은 그대로다.

검증: cargo tree -i getrandom@0.2 --target wasm32-unknown-unknown 결과 없음,
cargo clippy -p rhwp --lib --target wasm32-unknown-unknown -- -D warnings 통과,
cargo check --target wasm32-unknown-unknown --lib 통과,
agent_seal 단위 테스트 16/16 통과,
cargo clippy --workspace --all-targets 통과.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@jangster77

Copy link
Copy Markdown
Collaborator

통합 PR #4883(4412546)로 병합 완료했습니다.

원 head와 CI를 다시 확인해 누적 반영했고, 상세 검토·메인터너 보정·검증 근거는 archive 검토 기록에 남겼습니다.

중복 병합을 막기 위해 이 원 PR을 닫습니다. 감사합니다.

@jangster77 jangster77 closed this Aug 15, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants