HWP3 보안 트레일러 포스트양자 공개키 봉인(ML-KEM-768, FIPS 203) - #4838
Closed
kevin9327 wants to merge 6 commits into
Closed
Conversation
유효한 HWP3 파일 뒤에 강암호 트레일러를 append 해, 민감 필드는 가시층에서 가리고 그 원값은 비밀번호 보유자만 복원하게 한다. 순정 한컴은 가려진 정상 문서를 그대로 열람하고, rhwp 는 파일을 열 때 절대 에러로 죽지 않고 Plain/Sealed/Broken 으로 수렴한다. 정당한 범위: REDACTED 전용(문서 전체 위장/은닉 모드 없음). 평문 매직마커 (RHWPSEC1/RHWPEND1)로 시작·끝나 검사 도구가 즉시 식별 가능 — 내용을 암호화할 뿐 존재를 은폐(스테가노)하지 않는다. 가시 기밀성은 여전히 56비트(DES) 상한. - src/security_trailer.rs: seal(호스트,비밀,비번)=Argon2id(19MiB)→XChaCha20-Poly1305 (AAD=헤더 전체) append. open()=탐지→복호→Plain/Sealed/Broken 정상화(무패닉). detect_trailer/visible_layer. 포맷은 kdf_algo/aead_algo 바이트로 알고리즘 agility. - 검증: cargo test --lib security_trailer:: 7/7(왕복·오답비번→Broken·변조→AEAD거부· 무트레일러→Plain·우연MAGIC_END→Plain 오탐방어·재저장소실→Plain·빈비밀). - 의존성 argon2 0.5·chacha20poly1305 0.10·zeroize 1(RustCrypto·순수Rust·wasm 호환). - 후속: CLI(seal/unseal)·파서 통합(열 때 자동 복원)은 별도 PR. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
CI 가 WASM32 경로 3곳에서 실패했다 (PR edwardkim#4826): Lint 의 "Clippy (WASM32)", Frontend package gates 의 wasm-pack 빌드, Canvas visual diff 의 wasm 빌드. Build & Test·CodeQL 실패는 그 파생이다. 원인은 **getrandom 0.2** 다. argon2 의 기본 피처(`rand`·`password-hash`)와 chacha20poly1305 의 기본 피처(`getrandom`)가 rand_core 0.6 을 거쳐 0.2 를 끌어오는데, 0.2 는 wasm32-unknown-unknown 에서 `js` 피처 없이 compile_error! 로 죽는다. 이 저장소가 이미 켜 둔 `getrandom 0.4 + wasm_js` 는 다른 버전이라 그 자리를 못 덮는다. security_trailer 는 소금·논스를 `getrandom::fill`(0.4)로 직접 만들고 저수준 Argon2 API 와 논스를 넘겨받는 XChaCha20-Poly1305 만 쓴다 — 두 크레이트의 rand 계열 피처가 애초에 필요 없다. default-features 를 끄고 `alloc` 만 남겨 구버전 getrandom 을 그래프에서 제거한다. 검증: 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 통과, cargo clippy --workspace --all-targets 통과, security_trailer 단위 테스트 7/7 통과. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
CodeQL 이 이 PR 에서 critical 2건을 새로 잡았다 — rust/hard-coded-cryptographic-value, src/security_trailer.rs 의 테스트 상수 `PW` 와 오답용 `b"wrong"` 이 KDF 에 비밀번호로 흘러간다. 억제 주석으로 덮는 대신 원인을 없앤다. 테스트는 매 실행 `getrandom::fill` 로 32바이트 비밀번호를 새로 뽑고, 오답 경로는 두 번째 난수를 쓴다. 스캐너를 달래는 것만이 아니라 테스트로서도 낫다 — 왕복·변조·오답 판정이 특정 값에 우연히 기대지 않음을 매 실행이 재확인한다. 검증: security_trailer 단위 테스트 7/7 통과, cargo clippy -p rhwp --lib --target wasm32-unknown-unknown -- -D warnings 통과, cargo clippy --workspace --all-targets 통과, rustfmt 통과. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
기존 비밀번호 모드(Argon2id → XChaCha20-Poly1305)는 이미 양자내성이다 — 대칭 암호는 Grover 로 유효 강도가 절반(256→128비트)으로 줄 뿐 여전히 안전하다. 양자 (Shor)에 깨지는 건 비대칭 공개키 교환이다. 그래서 이 커밋은 비밀번호 공유 없이 수신자 공개키로 봉인하는 양자안전 공개키 교환(ML-KEM-768 격자 KEM)을 추가한다. - kdf=2(KDF_MLKEM768) 새 PQ 트레일러: EK=1184·DK=2400·CT(encap)=1088·SS=32. - generate_keypair / seal_to_pubkey / open_with_privkey 3함수 추가. - 32바이트 공유비밀을 그대로 XChaCha20-Poly1305 키로 사용, 헤더 전체(MAGIC..ct_len)를 AAD 로 묶어 어떤 헤더 변조도 복호 거부로 이어진다. - ML-KEM 역캡슐화는 무오류(묵시적 거부)이므로 진짜 무결성 관문은 AEAD 다: 틀린 개인키·변조 CT 는 다른 공유비밀 → AEAD 실패 → Broken(패닉 없음). - 개인키 경로는 checked 산술 기반 엄격 경계 파싱으로 어떤 길이 조작에도 인덱스 OOB/패닉 없이 Broken 으로 수렴한다. - 비밀번호(kdf=1) 경로는 무변경 — 공개키 트레일러가 open() 으로 들어오면 안내 메시지만 개선(동작은 그대로 Broken). - 순수 Rust(RustCrypto), wasm 호환. 정당한 범위는 기존과 동일(REDACTED 전용, 평문 매직마커로 탐지 가능, decoy 모드 없음). - 테스트 12건 추가(왕복·키크기·오키·CT변조·AAD변조·교차경로·빈비밀·잘못된 키길이); cargo test --lib security_trailer:: 19건 전부 통과(debug·release-test), clippy -D warnings·rustfmt 통과. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
…o gym_hwp3_pq_seal
CI 가 WASM32 3단계에서 실패했다 (PR edwardkim#4838): Lint 의 Clippy (WASM32), Frontend package gates 의 wasm-pack 빌드, Canvas visual diff 의 wasm 빌드. Build & Test 실패는 그 파생이다. 원인은 edwardkim#4826 과 같은 **getrandom 0.2** 로, 이 브랜치가 그 수정 이전 베이스에서 갈라져 나와 그대로 물려받았다. gym_hwp3_security_trailer 의 수정본을 병합해 함께 반영한다: - argon2·chacha20poly1305 의 기본 피처 차단(rand 계열이 rand_core 0.6 을 거쳐 getrandom 0.2 를 끌어온다 — wasm32 에서 compile_error!). ml-kem 경로는 이 PR 이 이미 getrandom 0.4 어댑터(GetRandomRng)를 두고 있어 추가 조치가 필요 없다. - 테스트 비밀번호를 상수에서 실행시 난수로(CodeQL critical 2건의 원인). 이 브랜치가 새로 추가한 PQ 테스트 2곳의 `PW` 참조도 같이 옮겼다. 검증: 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 통과, security_trailer 단위 테스트 19/19 통과(PQ 13건 포함), cargo clippy --workspace --all-targets 통과, rustfmt 통과. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
kevin9327
added a commit
to kevin9327/rhwp
that referenced
this pull request
Aug 15, 2026
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
self-requested a review
August 15, 2026 13:29
jangster77
pushed a commit
that referenced
this pull request
Aug 15, 2026
CI 가 WASM32 3단계에서 실패했다 (PR #4838): Lint 의 Clippy (WASM32), Frontend package gates 의 wasm-pack 빌드, Canvas visual diff 의 wasm 빌드. Build & Test 실패는 그 파생이다. 원인은 #4826 과 같은 **getrandom 0.2** 로, 이 브랜치가 그 수정 이전 베이스에서 갈라져 나와 그대로 물려받았다. gym_hwp3_security_trailer 의 수정본을 병합해 함께 반영한다: - argon2·chacha20poly1305 의 기본 피처 차단(rand 계열이 rand_core 0.6 을 거쳐 getrandom 0.2 를 끌어온다 — wasm32 에서 compile_error!). ml-kem 경로는 이 PR 이 이미 getrandom 0.4 어댑터(GetRandomRng)를 두고 있어 추가 조치가 필요 없다. - 테스트 비밀번호를 상수에서 실행시 난수로(CodeQL critical 2건의 원인). 이 브랜치가 새로 추가한 PQ 테스트 2곳의 `PW` 참조도 같이 옮겼다. 검증: 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 통과, security_trailer 단위 테스트 19/19 통과(PQ 13건 포함), cargo clippy --workspace --all-targets 통과, rustfmt 통과. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Collaborator
|
통합 PR #4883(4412546)로 병합 완료했습니다. 원 head와 CI를 다시 확인해 누적 반영했고, 상세 검토·메인터너 보정·검증 근거는 archive 검토 기록에 남겼습니다. 중복 병합을 막기 위해 이 원 PR을 닫습니다. 감사합니다. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
요약
기존 비밀번호 봉인 모드(#4826,
kdf=1: Argon2id → XChaCha20-Poly1305) 위에 포스트양자공개키 봉인 모드(
kdf=2: ML-KEM-768, NIST FIPS 203) 를 추가한다. 비밀번호를 공유하지않고 수신자의 공개키로 봉인하며, 여는 자격만 "비밀번호 보유"에서 "개인키 보유"로 바뀐다.
왜 "공개키 교환"만 양자안전으로 바꾸나 (정직한 프레이밍)
기존 비밀번호 모드는 이미 양자내성이다. 대칭 암호(XChaCha20-Poly1305, 256비트 키)는
Grover 로 유효 강도가 절반(→128비트)으로 줄 뿐 여전히 안전하고, Argon2id 도 대칭
원시함수다. 양자 컴퓨터(Shor)에 실제로 깨지는 건 비대칭 공개키 교환이다.
그래서 이 PR 은 "가시 내용을 더 세게 지진다"가 아니라, 비밀번호 공유 없이 공개키로
봉인하는 경로에서 그 유일한 양자취약점(비대칭 키교환)을 격자 KEM(ML-KEM)으로 대체한다.
공유비밀 32바이트를 그대로 AEAD 키로 쓰므로 대칭부는 종전과 완전히 동일하다.
무엇이 추가됐나
src/security_trailer.rs(leaf 파일 하나)와 의존성 2개(ml-kem = "0.2",rand_core = "0.6";둘 다 순수 Rust·wasm 호환).
pub const KDF_MLKEM768: u8 = 2;— ML-KEM-768 크기: EK=1184, DK=2400, CT(encap)=1088, SS=32.generate_keypair() -> (공개키 EK, 개인키 DK)seal_to_pubkey(host, secret, recipient_public) -> Result<Vec<u8>, SealError>open_with_privkey(bytes, secret_key) -> Opened— Plain/Sealed/Broken 로 수렴, 절대 패닉 없음.PQ 트레일러 레이아웃 (비밀번호 트레일러와 매직마커·꼬리 공유 →
detect_trailer그대로)AAD =
MAGIC..ct_len전체 헤더 → encap·nonce·길이·플래그 어느 것이든 변조하면 복호 거부.암호학적 정확성 (핵심)
공유비밀을 돌려줄 뿐
Err를 내지 않는다. 따라서 진짜 무결성 관문은 AEAD: 공유비밀이다르면 AEAD 키가 달라져 복호가 실패하고
Broken이 된다. 이 성질을 테스트로 못박았다.checked_add기반 엄격 경계 파싱 — 어떤 길이 필드가 조작돼도 인덱스OOB/패닉 없이
Broken으로 귀결한다.Zeroizing로 소거.비밀번호 경로는 무변경
kdf=1(Argon2id) seal/open 크립토 로직은 한 줄도 바꾸지 않았다. 유일한 diff 는open()의"미지원 알고리즘" 분기에서
kdf=2트레일러가 들어왔을 때 안내 메시지만 개선한 것이다(동작은 그대로
Broken— "개인키로 열어야 한다"고 알려줄 뿐).정당한 범위 (#4826 과 동일)
REDACTED 전용, 평문 매직마커(
RHWPSEC1/RHWPEND1)로 탐지 가능, 문서 전체를 숨기는decoy 모드 없음. 이 모드가 바꾸는 건 "여는 자격"(비밀번호 → 개인키)뿐이다. 렌더러 표면을
건드리지 않는 라이브러리 변경이라 시각 회귀 대상이 아니다.
검증
cargo test --lib security_trailer::— 19건 전부 통과(기존 비밀번호 7 + 신규 PQ 12),debug·release-test프로파일 양쪽.Broken·CT 변조→Broken·AAD(encap)변조→
Broken·비밀번호 경로로 PQ 열기→Broken·PQ 경로로 비밀번호 트레일러 열기→Broken·빈 비밀·잘못된 키 길이(패닉 없음) 등.rustfmt --edition 2021 --check src/security_trailer.rs통과(leaf 파일만),cargo clippy --lib --tests -- -D warnings통과.🤖 Generated with Claude Code