암호화
푸시 본문이 어떻게 암호화되며, 위협 모델이 무엇을 막고 무엇을 못 막는지.
종단간 암호화는 기본값이 꺼짐이고, 켜려면 Zeph Pro가 필요하다. 스위치는 Zeph 앱의
Settings → Encryption에 있으며, 켜기 전까지는 모든 푸시와 첨부가 평문으로 백엔드에 도착한다.
나중에 계정이 Pro를 잃으면 서버가 암호화된 전송을 PRO_REQUIRED로 거부하고 CLI는 같은 푸시를
평문으로 다시 보낸다.
켜져 있으면 푸시 본문과 긴 본문 첨부가 AES-256-GCM으로 암호화된다. 이 호스트는 자체 ECDH P-256
키쌍을 ~/.zeph/device-keys.json에 두며, 첫 사용 시 생성되고 개인키는 머신을 떠나지 않는다 —
백엔드는 공개키만 저장하고 개인키 업로드는 아예 거부한다.
각 푸시는 한 번 암호화되고, 그 AES 키는 계정의 디바이스마다 그 디바이스의 공개키에 대한 ECDH로 따로 래핑된다. 제목과 URL도 본문과 함께 암호문 안으로 들어가므로, 저장된 푸시에는 읽을 수 있는 필드가 남지 않는다.
위협 모델
수동적인 백엔드 — 유출된 스냅샷, 테이블을 들여다보는 운영자 — 에 대해서는 저장된 암호문과 래핑된 키가 쓸모없으므로 푸시 내용이 비밀로 남는다.
알아둘 한계가 셋 있다:
- 능동적으로 악의적인 운영자는 막지 못한다. 수신자 공개키는 같은 서버의
GET /devices에서 오며, 서명도 피닝도 되어 있지 않다. 자기 키를 담은 디바이스 레코드를 주입하는 백엔드는 메시지 키를 자기 몫으로 받아 전부 읽는다. 대응 수단은 Zeph 앱에 있다 — 디바이스마다 공개키의 SHA-256 지문이 표시되고, 나란히 비교한 뒤 그 디바이스를 verified로 표시하면 strict 모드가 verified 디바이스에만 래핑한다. 다만 기본값은 꺼짐이고, verified 목록은 동기화되지 않고 브라우저 프로필별로 저장되며, CLI와 MCP 서버는 이 목록을 보지 않는다 — 서버가 알려주는 모든 디바이스에 래핑한다. 즉 앱이 직접 보내는 푸시는 이걸로 막을 수 있고, CLI가 보내는 푸시는 막을 수 없다. - 전방 비밀성이 없다. 특정 발신자·디바이스 쌍의 ECDH 비밀은 고정이므로, 어느 한쪽 개인키가 털리면 그 쌍으로 래핑된 과거 푸시가 전부 열린다.
senderPublicKey는 서명되지 않는다. 그래서 바꿔치기하면 푸시를 복호화할 수 없게 된다. 이 방향은 정보가 새는 대신 닫히는 쪽으로 실패한다.
키가 없는 디바이스
디바이스별 공개키를 등록하지 않은 디바이스에는 보낼 수 없고, 건너뛴다. 자격을 갖춘 디바이스가 하나도 없으면 아무도 열 수 없는 것으로 도착하는 대신 평문으로 나간다.
listener는 복호화하지 않는다
zeph listener는 현재 isEncrypted 푸시를 무시한다 — 복호화를 시도하지 않는다. Stop hook 자동
푸시와 zeph_ask 응답은 주입 경로에 포함되지 않으므로 일반적인 사용에는 영향이 없다.