Skip to content

결정 필요: 캡슐 암호화 모델 — 설계 문서(E2E)와 현재 구현(서버 봉투 암호화) 불일치 #32

Closed

Description

@cfcromn

Describe

「🔐 암호화 설계」 문서와 현재 구현이 서로 다른 암호화 모델을 따르고 있다. 구현 작업 전에 어느 쪽으로 갈지 결정이 필요하다.

원래 #30 에 세 항목 중 하나로 묶여 있었으나, 나머지 두 항목이 #31 로 완료되면서 이 결정만 남아 별도 이슈로 분리한다.

무엇이 어긋나 있나

설계 문서 현재 구현 (#23)
CEK 생성 위치 클라이언트 서버 (MUDDA_MASTER_KEY 를 KEK 로 사용)
키 보관 tbl_key_share 에 Shamir 조각 분산 (2-of-3 기본, 3-of-5 강화) 서버가 KEK 를 보유
복호화 위치 클라이언트 서버 (EncryptedStringConverter)
열람 시 서버가 반환하는 것 키 조각 복호화된 평문 (OpenCapsuleResponse.content)
서버가 캡슐 내용을 볼 수 있나 볼 수 없음 볼 수 있음

현재 구현은 저장소 유출에 대한 at-rest 암호화로는 정상 동작한다. 다만 CLAUDE.md 의 다음 항목은 만족하지 못한다.

서버는 캡슐 내용을 절대 평문으로 볼 수 없다. 암호화/복호화 경계는 전용 보안 모듈에서 명시적으로 관리한다.

즉 문서와 코드 중 한쪽은 반드시 고쳐야 하며, 지금은 둘이 어긋난 상태다.

선택지

(A) 설계 문서대로 클라이언트 사이드 E2E 로 전환

  • tbl_key_share 테이블 추가 (capsule_id, share_index, share_data)
  • Shamir's Secret Sharing GF(256) 직접 구현
  • 캡슐 생성 API: 평문 content 대신 클라이언트가 암호화한 blob + 키 조각을 받도록 요청 계약 변경
  • 캡슐 열람 API: 평문 대신 키 조각을 반환하도록 응답 계약 변경
  • 위치·비밀번호 검증 통과 시에만 키 조각을 반환하도록 접근 제어
  • 만료 캡슐의 키 조각 삭제 스케줄러 (설계 문서에 명시된 항목)
  • 클라이언트 작업이 반드시 동반된다. 서버만으로는 완결되지 않는다.
  • 기존에 저장된 캡슐의 마이그레이션 방안도 필요하다. 서버가 KEK 로 복호화해 재암호화하려면 클라이언트가 개입해야 하므로 단순 배치로는 해결되지 않는다.

(B) 현재 서버 사이드 모델을 확정하고 문서를 수정

  • CLAUDE.md 의 "서버는 캡슐 내용을 절대 평문으로 볼 수 없다" 항목을 실제 위협 모델에 맞게 수정
  • 「🔐 암호화 설계」 문서를 봉투 암호화 방식으로 개정
  • 기술 스택에서 Shamir's Secret Sharing 제외
  • 코드 변경 없음, 문서 작업만 발생

Additional

포트폴리오 관점에서 (A) 는 서술할 거리가 크지만 클라이언트 작업과 기존 데이터 마이그레이션이 함께 걸린다. (B) 는 즉시 정리되지만 프로젝트의 핵심 컨셉인 "서버도 못 여는 캡슐" 이 약해진다.

(A) 를 선택할 경우 범위가 크므로 서버/클라이언트/마이그레이션으로 이슈를 다시 쪼개는 편이 낫다.

관련: #30, #31

Activity

  1. cfcromn commented on Aug 30, 2026

    @cfcromn
    CollaboratorAuthor

    A(설계대로 E2E 전환)로 진행하기로 하고 착수했습니다. 그런데 구현에 들어가기 전에 설계 문서를 다시 읽어보니 문서대로 만들면 E2E가 성립하지 않습니다. 먼저 정리해두고 나머지 구현 방향을 정해야 할 것 같습니다.

    문제

    설계 문서의 흐름은 이렇습니다.

    5. 서버는 blob → tbl_capsule.content_encrypted 저장
               키 조각들 → tbl_key_share에 각각 저장
    ...
    3. 서버가 tbl_key_share에서 키 조각들 반환
    

    즉 N개 조각이 전부 서버의 tbl_key_share 에 저장됩니다. 그러면 서버는 언제든 자기 테이블에서 조각을 다 읽어 합칠 수 있고, CEK 를 복원해 모든 캡슐을 열어볼 수 있습니다.

    Shamir 를 붙였지만 "서버는 캡슐 내용을 절대 볼 수 없다" 는 성질은 전혀 얻어지지 않습니다. CEK 를 그냥 평문으로 한 컬럼에 저장한 것과 보안상 동등합니다. 조각으로 나눠 저장하는 것 자체는 같은 DB 안에서는 아무것도 보호하지 못합니다.

    문서 안에서도 이 부분이 서로 어긋납니다.

    서버가 조각 일부를 갖고 있어도 단독으로 복원 불가

    "일부" 를 갖고 있을 때만 성립하는 문장인데, 저장 구조는 전부를 갖도록 되어 있습니다.

    이대로 구현하면 겉보기에는 E2E 인데 실제로는 아닌 코드가 됩니다. 포트폴리오 관점에서는 정직한 at-rest 암호화보다 오히려 불리합니다. 리뷰어가 tbl_key_share 스키마만 봐도 바로 드러납니다.

    성립하려면

    서버가 정족수(threshold)를 못 채우게, 최소 한 조각은 서버에 없어야 합니다. 후보는 셋인데 실제로 쓸만한 건 하나뿐입니다.

    방식 성립 여부
    위치에서 조각 유도 ❌ 서버가 ST_DWithin 때문에 좌표를 반드시 갖고 있으므로 서버도 유도 가능
    수신자 기기에 조각 보관 △ 성립하지만 기기 분실 = 캡슐 영구 소실, PUBLIC 캡슐에는 적용 불가
    비밀번호·질문 답에서 조각 유도 ✅ 서버는 bcrypt 해시만 갖고 있어 유도 불가

    세 번째가 이 서비스의 잠금 모델과 그대로 맞물립니다.

    • 서버는 threshold - 1 개 조각만 저장
    • 마지막 조각은 클라이언트가 KDF(비밀번호 또는 정답) 로 유도
    • 서버는 위치·비밀번호 검증 후 자기 조각들을 반환하고, 클라이언트가 마지막 조각을 만들어 정족수를 채움
    • 서버는 정답을 모르므로 혼자서는 영원히 복원 불가

    다만 여기에 걸리는 제약

    lockType = NONE 인 캡슐은 E2E 가 원리적으로 불가능합니다.

    잠금이 없는 캡슐의 열람 조건은 "그 좌표에 간다" 뿐이고, 그 조건은 서버가 전부 알고 있습니다. 서버가 스스로 만족시킬 수 있는 조건만으로 열리는 캡슐은, 정의상 서버도 열 수 있습니다. 이건 구현으로 우회할 수 있는 게 아니라 제품 규칙에서 따라 나오는 결론입니다.

    그래서 선택이 필요합니다.

    • (A-1) 잠금 있는 캡슐만 E2E — PASSWORD/QUESTION 캡슐은 서버도 못 열고, NONE 캡슐은 지금처럼 서버 봉투 암호화. 캡슐 종류에 따라 보장 수준이 다르다는 걸 문서와 API 에 명시.
    • (A-2) 모든 캡슐에 잠금 필수 — lockType = NONE 을 제거해서 전 캡슐 E2E. 보장은 깔끔해지지만 "지나가다 발견해서 열어보는" 경험이 사라집니다.
    • (A-3) 수신자 기기 보관 병행 — 지정 수신자가 있는 캡슐은 기기에 조각을 두고, 그 외에는 A-1 규칙. 구현·UX 복잡도가 가장 큽니다.

    지금까지 한 것

    어느 방식으로 가든 필요한 Shamir GF(256) 구현을 먼저 넣었습니다 (6083e31).

    • GaloisField256 — AES 기약다항식(0x11B) 기반 log/exp 테이블 연산
    • ShamirSecretSharing — 바이트 단위 분할, Lagrange 보간 복원, 2-of-3 / 3-of-5 지원
    • 테스트 24개: 체 공리(결합·분배·역원), 임계값 미만 조각으로는 복원 불가, 1개 조각이 256개 후보 전부와 모순 없음(정보이론적 안전성), 0x00·0xFF 경계, 최대 255 조각

    tbl_key_share 스키마와 API 계약 변경은 위 A-1/A-2/A-3 결정에 따라 달라져서 아직 손대지 않았습니다.

    추가로 확인이 필요한 것

    기존에 저장된 캡슐 처리 방안입니다. 현재 캡슐은 서버 KEK 로 암호화되어 있어 E2E 로 옮기려면 평문을 거쳐야 하고, 그건 클라이언트가 해야 합니다. 운영 데이터가 아직 없다면 마이그레이션 없이 전환 가능하지만, 있다면 별도 계획이 필요합니다.

  2. added 4 commits that reference this issue on Aug 30, 2026
  3. exijn commented on Aug 31, 2026

    @exijn
    Member

    연결된 PR #33이 develop에 병합되어 완료된 작업으로 확인되어 종료합니다.

    PR: #33

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions