캡슐 본문 AES-256-GCM 봉투 암호화 모듈 구현 - #24
Merged
Merged
Conversation
@lob on a String maps to a PostgreSQL large object, so the TEXT column held an OID pointing into pg_largeobject rather than the content itself. The referenced large object is also not removed when the row is deleted, orphaning user content indefinitely. TEXT is unbounded, so the indirection bought nothing.
Puts the crypto boundary in a converter rather than the service layer, so a service cannot forget to encrypt. Also drops @lob from the field for the same reason as the guestbook fix: it would have stored a pg_largeobject OID in the column instead of the cipher envelope.
hej090224
reviewed
Aug 17, 2026
Member
|
이 바이트 때문에 git/GitHub이 파일 전체를 바이너리로 인식해서 이 PR diff에서 "Binary files differ"로만 표시되고, 인라인 코멘트도 걸 수 없는 상태였습니다(라인 diff/blame도 이후 계속 깨질 것으로 보입니다). 유니코드 이스케이프(U+0000)로 바꾸면 의도한 컨트롤 문자 테스트는 그대로 유지하면서 파일이 다시 텍스트로 인식됩니다. |
…ng it raw A raw 0x00 in the source made git classify the whole file as binary, so this PR showed it as "Binary files differ" and line diffs, blame and inline review comments were all unavailable on it. The \u0000 escape compiles to the same string, so the control-character round-trip is still covered.
Collaborator
Author
|
4766cb2 에서 반영했습니다. 정확한 지적이었고, 재현까지 확인했습니다. 수정 후: 원시 0x00을 PR의 나머지 변경 파일에도 NUL 바이트가 있는지 전수 확인했고 이 파일 하나뿐이었습니다. 이 코멘트 덕분에 diff/blame이 살아났습니다. 감사합니다. |
hej090224
approved these changes
Aug 19, 2026
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.
✨ 작업 내용
캡슐 본문을 AES-256-GCM 봉투 암호화로 저장하는 모듈을 추가했습니다. timecapsule 도메인 API의 선행 작업입니다.
global/crypto/신규 —CryptoProperties,CryptoConfig,ContentCipher,EncryptedStringConverterTimeCapsule.content에@Convert부착 (기존 파일 변경은 이 지점과 아래@Lob수정뿐)mudda.crypto.master-key설정 추가 + 기동 시 32바이트 검증저장 포맷
컬럼 추가 없이 기존
content TEXT에 자기서술적 blob 하나로 저장합니다. 기존 데이터가 0건이라 Flyway 마이그레이션이 필요 없습니다.캡슐마다 DEK를 분리한 이유는 두 가지입니다. ① 키 회전 시 본문 전체 재암호화 대신 캡슐당 wrapped DEK만 다시 감싸면 됩니다. ② 단일 키로 암호화하는 데이터 양을 작게 유지해 GCM nonce 충돌을 비이슈로 만듭니다.
v1태그는 미리 넣되 키는 1개만 지원합니다. 실제 회전이 필요해지면 다중 키 맵으로 확장하며, 포맷이 준비돼 있어 재마이그레이션이 없습니다.🔍 리뷰 시 참고사항
MUDDA_MASTER_KEY를 배포 시크릿에 먼저 등록해야 합니다. 기본값을 일부러 주지 않았으므로 미설정 시 애플리케이션이 기동에 실패합니다. 그리고 이 키를 분실하면 저장된 모든 캡슐 본문이 영구 복구 불가입니다.작업 중 발견한 버그를 함께 고쳤습니다 (범위 확대)
TimeCapsule.content와Guestbook.content에 붙어 있던@Lob이 실제로는 PostgreSQL large object 매핑이었습니다. 통합 테스트에서 원시 컬럼을 읽어보니 값이19921— 본문이 아니라pg_largeobject를 가리키는 OID였습니다.TEXT인데 엔티티는 large object를 쓰고 있어 선언과 실제가 어긋난 상태였습니다TEXT는 길이 제한이 없어 이 우회로 얻는 이득이 없습니다@Lob을 제거해 두 엔티티 모두 컬럼에 직접 쓰도록 고쳤습니다. 데이터가 아직 0건이라 마이그레이션 없이 정리 가능한 시점이었습니다.제외 범위
Guestbook.content암호화 — 이번 PR은@Lob버그만 같이 고치고 암호화는 적용하지 않았습니다. 방명록은 캡슐을 연 사람들이 공유하는 글이라 "봉인된 본문"과 기밀성 등급이 다르고, 캡슐 본문과 달리 페이지 단위로 N건씩 조회되어 복호화 비용 구조가 다릅니다. feat: 방명록 본문 암호화 적용 검토 및 적용 #25 에서 적용 여부와 projection 전략을 함께 다룹니다.passwordHash/answerHash해싱설계 결정 (반박 환영)
Cipher.getInstance(..., "BC")1줄입니다.bcprov의존성은 남겨뒀습니다.lockType = NONE이 기본 시나리오라 사용자 제공 비밀이 없고, 좌표는 지오펜싱 때문에 어차피 평문이라 파생 키로 쓸 수 없습니다. 따라서 서버가 탈취되면 복호화가 가능합니다. DB 덤프 유출은 막지만 CLAUDE.md의 "서버는 평문을 볼 수 없다"를 문자 그대로 만족하지는 않습니다. 이걸 완전히 만족하려면 클라이언트 사이드 암호화가 필요하고, 그건 백엔드 범위를 넘습니다.content를 제외한 projection을 써야 합니다. 컨버터 주석에도 남겨뒀습니다.테스트
EncryptedContentIntegrationTest가 네이티브 쿼리로 원시 컬럼을 직접 읽어 평문 부재를 단언합니다. JPA로 읽으면 그대로 복호화되기 때문에, 이 방식만이 "평문을 저장하지 않는다"를 검증 가능한 주장으로 만듭니다. Hibernate가 컨버터에ContentCipher빈을 주입하는지도 이 테스트가 함께 보장합니다.✅ 체크리스트
.env.example등) 변경이 필요한 경우 작성 또는 수정했나요?📎 관련 이슈(선택)