보안 설계
폰에서 잠그고,
폰에서 엽니다.
Krypton 앱에서 글과 사진을 잠그고 여는 암호는 iPhone과 Android 앱이 함께 쓰는 보안 코어 하나에서 돌아갑니다.
01 봉인
글 한 편이
봉인되어 가는 길
글을 올리면 내 폰이 먼저 잠급니다. 서버는 잠긴 글과 봉투를 받아 두었다가 건넬 뿐, 열지 못합니다. 답과 사진, 영상도 같은 길로 갑니다.
글쓴이의 폰이 보안 코어 안에서 이 글만을 위한 새 콘텐츠 열쇠를 만듭니다.
그 열쇠로 글과 사진·영상을 잠급니다. 글은 정확한 길이가 드러나지 않게 정해진 크기까지 채운 뒤 잠급니다.
받는 사람마다 콘텐츠 열쇠를 그 사람의 암호화 열쇠로 봉해 봉투를 하나씩 만듭니다. 글쓴이 자신의 봉투도 하나 있습니다.
서버는 암호문과 봉투를 받아 두었다가 받는 사람에게 건넵니다. 봉투를 여는 열쇠는 받는 사람의 폰에만 있어 서버는 열지 못합니다.
받는 사람의 폰은 보안 코어 안에서 자기 봉투를 열어 콘텐츠 열쇠를 꺼냅니다.
그 열쇠로 글을 풀어 화면에 보여 줍니다.
무슨 뜻인가요
- 서버에 있는 내용은 암호문뿐입니다
- 글·답·사진·영상은 서버에 닿기 전에 이미 잠겨 있습니다.
- 봉투가 없으면 열지 못합니다
- 봉투는 받을 사람마다 하나씩 만들고, 그 사람의 폰만 엽니다. 봉투가 없는 사람은 암호문을 손에 넣어도 읽지 못합니다. 다만 받은 사람이 글을 건네거나 신고하면 그 사람의 폰이 새 봉투를 만듭니다 — 신고한 글은 Krypton 운영자가 엽니다.
- 글마다 새 열쇠
- 콘텐츠 열쇠는 글과 답마다 새로 만듭니다. 한 글의 열쇠로 다른 글을 열 수 없습니다.
- 주고받은 사이는 서버가 압니다
- 봉투를 건네려면 누가 누구에게 언제 보냈는지 알아야 합니다. 무엇을 썼는지는 모릅니다.
- 서버가 건네는 명단과 열쇠를 믿습니다
- 폰은 받는 사람의 명단과 공개 열쇠를 우리 서버에서 받고, 그 열쇠가 그 사람의 계정 열쇠로 서명됐는지만 확인합니다. 앱 안에서 서로의 열쇠를 맞춰 볼 방법은 아직 없습니다. 그래서 서버에 가짜 회원·가짜 1촌·가짜 열쇠가 끼어들면 폰이 그쪽에도 봉투를 만들어 줄 수 있습니다.
자세히
- 콘텐츠 열쇠
- 글·답마다 운영체제의 난수로 새로 만드는 32바이트 열쇠. 보안 코어 안에만 있고, 앱은 그 열쇠를 가리키는 무작위 번호표(핸들)만 받습니다.
- 본문
- XChaCha20-Poly1305로 잠그고 인증합니다. nonce는 무작위 24바이트. 봉인하기 전에 글을 크기 단위까지 채웁니다 — 256바이트에서 시작해 두 배씩, 64KiB 위로는 64KiB씩.
- 사진·영상
- 사진은 보안 코어에서 한 번에, 영상은 64KiB 덩어리마다 인증하며 파일째 잠급니다. 사진·영상은 크기를 채우지 않아 파일 크기는 드러납니다.
- 봉투
- 104바이트 — 일회용 공개 열쇠 32, nonce 24, 잠긴 콘텐츠 열쇠 32와 인증 태그 16. 봉투마다 새 일회용 X25519 열쇠 쌍을 만들어 받는 사람의 암호화 공개 열쇠와 공유 비밀을 얻고, HKDF-SHA256(두 공개 열쇠를 salt로)으로 봉투 열쇠를 만들어 XChaCha20-Poly1305로 잠급니다.
02 암호
보안 코어의 암호는
여섯 가지입니다
어느 것도 새로 지어낸 암호가 아닙니다. 모두 널리 쓰이는 표준이고, 보안 코어는 Rust 라이브러리(dalek, RustCrypto)의 구현을 씁니다. 봉투와 큰 파일을 잠그는 형식은 이 표준들을 엮어 우리가 정했고, 열쇠 조각을 나누는 Shamir 분할은 코어 안에 직접 구현했습니다.
| 암호 | 쓰는 곳 |
|---|---|
| X25519 | 봉투를 봉할 때. 받는 사람만 열 수 있게 콘텐츠 열쇠를 잠급니다. 봉투마다 새 일회용 열쇠 쌍과 받는 사람의 공개 열쇠를 씁니다. 모두 0이 되는 공유 비밀은 거부합니다. |
| Ed25519 | 서명. 계정과 그 기기가 진짜임을 보입니다. 계정 신원 열쇠와 기기마다의 기기 열쇠. 계정 열쇠가 기기 인증서와 계정의 암호화 열쇠에 서명합니다. |
| XChaCha20- | 잠그기. 봉인하는 모든 것을 잠그고, 누가 손을 대면 알아챕니다. 글 본문, 봉투, 감싼 열쇠, 되찾기 보관본, 사진·영상. nonce는 무작위 24바이트이고, 파일은 64KiB 덩어리마다 무작위 기본 nonce에 덩어리 번호를 섞습니다. |
| HKDF-SHA256 | 열쇠 만들기. 비밀 하나에서 한 가지 쓰임에 맞는 열쇠를 뽑습니다. X25519 공유 비밀에서 봉투 열쇠를(두 공개 열쇠를 salt로), 두 반쪽에서 되찾기 비밀을 만듭니다. 쓰임마다 라벨이 다릅니다. |
| Shamir 비밀 분할 GF(28) | 열쇠 조각 만들기. 되찾기에 쓰는 비밀의 한 반쪽을 1촌들에게 맡길 조각으로 나눕니다. 정해진 수의 조각이 모이면 되살아나고, 그보다 적으면 아무것도 알 수 없습니다. 체(field) 연산은 표 조회 없이 상수 시간으로 계산합니다. |
| OS 난수 생성기 | 짐작할 수 없는 값. 새로 만드는 모든 열쇠와 nonce를 운영체제의 난수로 만듭니다. rand_core의 OsRng(getrandom). 열쇠 조각의 계수도 여기서 얻습니다. 봉투 열쇠와 되찾기 비밀은 난수로 만들지 않고 HKDF로 이끌어 냅니다. |
03 열쇠
열쇠가
있는 곳
개인 열쇠는 내 폰 안, 앱의 보안 코어에 있습니다. 화면을 그리는 앱 코드조차 그 열쇠를 받지 못합니다.
- 번호표(핸들)
- 풀린 글·사진
개인 열쇠는 넘어가지 않습니다
- 개인 열쇠 넷
- 콘텐츠 열쇠
- 잠긴 개인 열쇠
- 이 폰에서만
- 공개 열쇠
- 잠긴 되찾기 보관본
- 개인 열쇠 넷
- 계정 열쇠, 기기 열쇠, 암호화 열쇠, 그리고 새 폰에서 되찾는 동안만 쓰는 임시 열쇠입니다.
- 이 폰에서만
- 열쇠는 iPhone에서는 키체인에, Android에서는 키스토어의 열쇠로 잠근 파일에 둡니다. 어느 쪽도 백업으로 다른 폰에 옮겨지지 않고, iPhone은 폰 잠금을 푼 동안에만 꺼냅니다. 새 폰은 되찾기로만 열쇠를 다시 받습니다.
- 앱 코드에는 번호표만
- 개인 열쇠를 앱의 JavaScript로 돌려주는 함수는 없습니다. 콘텐츠 열쇠도 코어 안에 두고, 앱은 그 열쇠를 가리키는 번호표만 받습니다. 번호표는 열쇠가 아니어서, 앱 밖으로 새어도 그것만으로는 아무것도 풀 수 없습니다.
- 코어가 지키는 것은 열쇠입니다
- 풀린 글과 사진은 화면에 보여 주려고 앱으로 옵니다. 폰 안에서 열린 내용까지 코어가 감추지는 않습니다.
자세히
- 개인 열쇠
- 보안 코어(Rust) 안의 자리 넷 — 계정(Ed25519), 기기(Ed25519), 암호화(X25519), 그리고 새 폰에서 되찾는 동안의 임시 받는 열쇠(X25519).
- 콘텐츠 열쇠
- 항목마다 새로 만드는 무작위 열쇠. 코어 안의 표에만 있고, 앱은 무작위 16바이트 핸들만 받습니다. 핸들은 열쇠 재료가 아닙니다.
- 보관
- iOS는 키체인(이 기기에서만, 잠금을 푼 동안에만). Android는 꺼낼 수 없는 Android Keystore의 AES-256-GCM 열쇠(StrongBox가 있으면 거기)로 잠가 백업되지 않는 폴더에 둡니다. 개인 열쇠가 코어 밖으로 나오는 때는 네이티브 래퍼(Swift·Kotlin)가 이곳에 쓰고 읽을 때뿐이고, 래퍼는 쓴 사본을 곧바로 0으로 덮습니다.
- 새 대화 미리보기 (iPhone)
- 새 대화가 오면 iPhone의 확장 프로그램이 같은 보안 코어로 봉인된 사본을 열어 누가 썼는지 보여 줍니다. 이 확장은 앱과 함께 쓰는 키체인 그룹에서 암호화 열쇠 하나만 읽고, 폰이 잠겨 있으면 읽지 못합니다.
- 열쇠 경계 검사
- 앱 코드(src 폴더)에 개인 열쇠를 다룰 만한 이름(privateKey 등)이 나타나면 검사 스크립트가 실패합니다. 이름을 찾는 보조 검사입니다. 개발·시험용 JavaScript 코어만 예외이고, 출시 빌드에서는 그 코어를 쓸 수 없습니다.
- 서버에 있는 내 열쇠
- 공개 열쇠(계정·기기·암호화)와 기기 인증서. 되찾을 방법을 준비했다면 잠긴 보관본, 되찾기 비밀의 서버 쪽 반쪽, 1촌들에게 봉한 열쇠 조각도 있습니다(05).
04 점검
만듦새와
점검
보안 코어는 메모리를 안전하게 다루는 언어인 Rust로 만들었고, 우리가 쓴 부분에서 안전 검사를 끄는 코드(unsafe)는 앱과 이어지는 경계의 두 파일에만 있습니다. 내부에서 검토했지만, 독립된 외부 감사는 아직 받지 않았습니다.
- 0으로 덮기
- 보안 코어 안의 열쇠와 풀린 바이트, 되찾기에 쓰는 비밀과 열쇠 조각은 놓일 때 0으로 덮이는 버퍼에 둡니다. 코어가 결과를 Swift·Kotlin 쪽에 건넬 때 쓰는 코어의 버퍼도 놓을 때 덮습니다. 콘텐츠 열쇠는 표가 커질 때 옛 자리에 사본이 남지 않게 따로 두고, 파일을 잠그고 풀 때는 표준 입출력 버퍼를 거치지 않아 평문이 그 버퍼에 남지 않습니다. 시험이 되찾기 준비, 콘텐츠 열쇠 표가 커질 때, 파일 잠그기·풀기, C 경계의 풀기에서 놓인 힙 메모리를 훑어 비밀이 남지 않았는지 봅니다.
- 덮지 못하는 곳
- 0으로 덮기는 할 수 있는 데까지입니다. 스택이나 레지스터에 잠깐 남는 사본, 쓰는 라이브러리 안쪽의 상태(HKDF의 중간 열쇠 등), 운영체제 라이브러리가 만든 내부 사본은 덮지 못합니다. Swift·Kotlin 쪽으로 건너간 사본은 개인 열쇠만 곧바로 덮고, 화면에 보여 줄 평문과 되찾기에 쓰는 서버 쪽 반쪽은 덮지 않습니다. Android 경계에서의 덮기는 힙을 훑는 시험으로 확인하지 못합니다(그 시험은 개발 컴퓨터에서만 돌아갑니다).
- 풀어 둔 사진·영상
- 다시 풀지 않고 열리도록 풀린 채로 폰 안의 캐시에 둡니다. 계정을 없애면 함께 지웁니다.
- unsafe 코드
- 크레이트 전체가
unsafe를 금지합니다(deny). Swift 경계(C ABI)와 Kotlin 경계(JNI) 두 파일만 허용합니다. 쓰는 라이브러리(curve25519-dalek 등)와 Rust 표준 라이브러리 안에는 unsafe 코드가 있습니다. - 경계에서의 실패
- 코어 코드를 실행하는 C 진입점은 모두 패닉을 잡습니다. 코어 안의 패닉은 앱을 멈추지 않고 오류 코드로 돌아옵니다. 메모리가 모자라 프로세스가 멈추는 경우는 잡지 못합니다. Kotlin 경계도 같은 C 진입점을 부릅니다.
- 큰 파일
- 64KiB 덩어리마다 인증하며 잠급니다. 자르기·순서 바꾸기·덧붙이기·다른 파일의 덩어리 섞기는 모두 풀기에 실패합니다. 풀린 파일은 끝까지 확인된 뒤에만 생기고, 실패하면 아무것도 남지 않습니다.
- 시험 벡터
- Rust 코어와 앱의 JavaScript 개발용 코어는 같은 바이트 형식을 씁니다. 각자 상대가 만든 고정 시험 벡터를 열어야 통과합니다.
- 시험
- Rust 단위 시험, 공유 시험 벡터, C 헤더가 내보낸 함수와 맞는지 보는 시험, 놓인 메모리에 비밀이 남는지 보는 시험이 있습니다. Swift 경계는 호스트에서 함수들을 불러 보고, Kotlin 경계는 Android 타깃으로 빌드해 검사합니다.
- 검토
- 출시 전 내부 검토가 일곱 영역을 다뤘습니다 — 데이터베이스 권한, 서버 함수, 요청 제한, 미디어 저장소, 앱, 운영 도구, 비밀 값. 코드를 읽고, 로컬에 띄운 서버 사본에 공격을 시도했습니다. 독립된 외부 감사는 아직 없습니다.
05 되찾기
새 폰에서
계정을 되찾기
폰을 잃어도 계정을 되찾을 수 있게, 미리 PIN을 정하고 열쇠 조각을 1촌들에게 맡겨 둡니다. 되찾을 때는 조각을 맡은 1촌이 전화로 나를 확인하고, 새 폰 화면의 숫자 여섯 자리를 듣고 쳐 넣어 맞을 때만 조각을 새 폰으로 보냅니다.
되찾는 길
- 준비
- 계정 열쇠와 암호화 열쇠를 내 폰에서 잠가 보관본으로 서버에 둡니다. 보관본을 여는 비밀은 두 반쪽에서 만듭니다 — 한 반쪽은 서버에, 다른 반쪽은 열쇠 조각으로 나눠 내가 고른 1촌 2~7명에게 한 사람씩 봉해 맡깁니다.
- 잃어버렸을 때
- 아는 1촌이 되찾기 링크를 보내 주면 새 폰에서 열고 PIN을 넣습니다. PIN은 폰에서 scrypt로 바꿔 보내고, 서버가 되찾기를 시작하게 하는 문일 뿐 열쇠를 여는 데는 쓰지 않습니다. PIN을 잊었으면 조각을 맡은 1촌이 넷 이상일 때만 비상 경로로 시작합니다.
- 숫자 여섯 자리
- 조각을 맡은 1촌은 전화로 나인지 확인하고, 새 폰 화면의 숫자 여섯 자리를 듣고 쳐 넣습니다. 숫자가 맞아야 그 1촌의 폰이 조각을 새 폰의 열쇠로 봉해 보냅니다. 서버가 중간에서 새 폰의 열쇠를 바꿔치기하면 숫자가 달라집니다.
- 기다림
- 새 폰이 요청을 시작하고 48시간이 지나고, 그 사이 조각을 맡은 1촌 둘이 도와야 되찾습니다. 비상 경로는 14일과 넷의 도움이 필요합니다. 그동안 옛 폰에서 그 요청을 보고 취소할 수 있습니다.
- 되찾음
- 기다림이 끝나면 새 폰이 조각을 모아 서버의 반쪽과 합쳐 보관본을 열고, 두 열쇠를 보안 코어에 들입니다. 새 폰은 새 기기 열쇠를 만들고, 옛 기기들은 끊겨 다시 로그인하지 못합니다(이미 받아 둔 로그인은 길어야 하루 안에 끝납니다).
자세히
- 서버가 가진 것
- 잠긴 보관본, 되찾기 비밀의 서버 쪽 반쪽, 1촌들에게 봉한 열쇠 조각(봉인된 채), PIN을 scrypt로 바꾼 확인값. 서버가 가진 것만으로는 보관본이 열리지 않습니다 — 조각 둘이 함께 있어야 합니다.
- 열쇠 조각
- Shamir 비밀 분할(GF(2⁸))로 나눕니다. 조각 둘이 모이면 반쪽이 되살아나고, 하나로는 아무것도 알 수 없습니다. 조각은 1촌마다 그 사람의 암호화 공개 열쇠로 봉합니다. 두 반쪽에서 HKDF-SHA256으로 되찾기 비밀을 만듭니다.
- 숫자 확인
- 먼저 묶고 나중에 엽니다. 새 폰은 1촌마다 다른 난수를 고르고, 자기 임시 공개 열쇠와 그 난수를 묶은 해시(묶음)만 먼저 보냅니다. 1촌의 폰은 묶음을 적어 둔 뒤 자기 난수를 내고, 새 폰은 그 난수를 적어 둔 뒤 자기 난수를 엽니다. 숫자는 새 폰의 임시 공개 열쇠와 두 난수를 SHA-256으로 섞은 값의 여섯 자리입니다. 서버는 한쪽 난수를 모른 채 먼저 정해야 해서, 바꿔치기한 열쇠로 숫자를 맞힐 확률은 1촌 한 사람과 견줄 때 많아야 100만분의 7(조각을 맡은 1촌이 일곱일 때)이고, 조각 둘을 얻으려면 두 사람 모두에게서 맞혀야 합니다.
- 링크와 PIN
- 되찾기 링크는 서버에 비밀 열쇠를 섞은 해시(HMAC)로만 남고, 요청이 끝나고 사흘 뒤 지웁니다. PIN은 링크를 만든 1촌마다 하루 다섯 번까지 틀릴 수 있습니다. 요청은 30일이 지나면 끝납니다.
- 마무리
- 되찾은 계정 열쇠로 새 기기 인증서에 서명해야 마무리됩니다. 링크와 서버 사본만으로는 마무리할 수 없습니다.
06 서버
서버가
보는 범위
서버는 잠긴 것을 받아 두었다가 건넵니다. 그러려면 누가 누구와 이어져 있고 누가 누구에게 보냈는지는 알아야 합니다. 무엇을 썼는지, 그 사람의 이름이 무엇인지는 모릅니다.
읽지 못하는 것
- 글과 대화
- 글·답·지인평·사진·영상과 링크 미리보기. 공감의 종류와 어느 답에 달았는지도 봉인 안에만 있습니다.
- 명함
- 이름·한 줄 소개·대부·흐린 사진. 명함은 기본으로 1촌과 2촌에게 한 사람씩 봉합니다. 공개를 고른 사람의 명함만 회원 누구나 가진 회원 열쇠로도 봉합니다. 대부는 계정 번호로만 싣습니다 — 누가 대부인지는 연결 지도로 서버도 압니다.
- 건넨 명함
- 1촌을 청할 때, QR·링크로 만날 때, 초대를 청할 때, 대화에 함께 있을 때, 사진 저장이나 화면 캡처를 알릴 때는 내 명함을 그 한 사람의 열쇠로 봉합니다. QR·링크와 초대 요청은 링크의 # 뒤에만 있는 비밀에서 뽑은 열쇠로 봉하고, 그 비밀은 서버로 가지 않습니다. 청이나 링크가 끝나면, 대화에서는 그 사람이 나가면 지우고, 사진 저장·캡처를 알린 명함은 그 기록과 함께 30일 뒤 지웁니다.
- 전화번호·이메일
- 받지 않습니다. 가입과 로그인은 폰 안의 열쇠(계정 열쇠·기기 열쇠)로만 합니다.
볼 수 있는 것
- 연결 지도
- 누가 누구와 1촌인지와 누가 누구에게 1촌을 청했는지, 누가 누구를 초대하고 확인했는지, 그 시각. 명함과 사진을 1·2촌에게 봉하는 일과, 두 사람이 확인해 들어오는 가입이 이 지도에 기댑니다.
- 주고받은 사이
- 글마다 봉투를 받은 사람, 대화 한 줄마다 쓴 사람과 받은 사람, 그 시각. 누가 누구에게 글을 건넸는지도 압니다. 대화마다 지금 있는 사람과 들고 난 때도 압니다.
- 명함을 공개했는지
- 명함을 누구에게 건네야 하는지 알아야 해서, 공개를 골랐는지는 압니다.
- 계정 둘레
- 계정 번호와 공개 열쇠, 회원 번호와 역할(창립자·팀)·Krypton+ 기간, 가입 상태, 차단·신고 관계, 화면 캡처·사진 저장을 누가 누구에게 알렸는지, 푸시를 보낼 기기 토큰과 언어, 되찾기를 준비했는지와 조각을 맡은 1촌, 진행 중인 되찾기를 누가 언제 도왔는지와 숫자가 달랐는지, 파일 크기와 글의 크기 단위.
- 접속 기록
- 우리 데이터베이스와 함수 기록에는 접속 IP를 남기지 않습니다. 초대장 없이 가입을 시작할 때만, 같은 곳에서 몇 번 시작했는지 세려고 IP 앞부분을 날마다 바뀌는 열쇠로 섞은 값을 이틀 둡니다. 서버를 운영하는 플랫폼(Supabase·Cloudflare)의 접속 기록에는 IP가 남습니다.
자세히
- 운영자
- 신고가 들어오면 신고한 사람의 폰이 그 글(또는 그 사람의 명함)의 열쇠를 운영자 열쇠로 봉해 보냅니다. 운영자 열쇠로 열리는 것은 신고된 것뿐이고, 운영자 열쇠는 앱 밖, 운영자의 컴퓨터에만 있습니다. Krypton 팀 계정은 회원 계정이라, 팀에게 보낸 가입 요청과 회원 열쇠로 열리는 공개 명함을 엽니다(07).
- 회원 열쇠
- 공개를 고른 명함을 회원 누구나 열게 하는 열쇠 하나입니다. 회원의 폰들이 서로에게 봉해 나누고, 회원이 빠지면 다음에 앱을 연 폰이 새 세대를 세웁니다. 서버는 이 열쇠를 갖지 않습니다.
- 2촌 명단
- 명함을 2촌에게 봉하려면 2촌이 누구인지 서버가 알려 줍니다. 서버가 이 명단을 속이는 경우는 07을 보세요.
07 아직
아직
갖추지 못한 점
지금 갖추지 못했거나, 알고 받아들인 한계입니다.
- 독립된 외부 감사가 없습니다
- 보안 코어와 서버는 내부에서만 검토했습니다. 2026년 10월 다시 검토하며 0으로 덮기의 빈틈과, 되찾을 때 서버가 새 폰의 열쇠를 바꿔치기할 수 있던 빈틈을 찾아 고쳤습니다(05의 숫자 확인 — 조각을 맡은 1촌이 새 앱을 받아야 닫힙니다).
- 서버가 건네는 명단을 아직 믿습니다
- 폰이 받는 사람을 창립자부터 이어진 회원 증서와 1촌 두 사람의 서명으로 확인하는 장치를 만들어 두었지만, 지금 앱은 계산만 하고 막지 않습니다. 그 전까지는 서버에 가짜 회원·가짜 1촌·가짜 2촌이 끼어들면 폰이 그쪽에도 명함과 글의 봉투, 회원 열쇠(공개 명함을 모두 여는 열쇠), 되찾기 조각을 봉해 줄 수 있습니다. 되찾기를 준비할 때 서버가 조각을 맡을 1촌의 열쇠를 바꿔치기하면 숫자 확인 없이 계정 열쇠를 얻을 수 있고, 이 길도 이 확인을 켜야 닫힙니다.
- 공개 명함은 회원 열쇠로 열립니다
- 회원 한 사람의 폰과 서버 사본을 함께 손에 넣은 사람은 공개 명함을 모두 읽을 수 있습니다. 지금은 회원이 빠질 때 세우는 새 세대가 옛 세대를 감싸 들고, Krypton 팀 기기도 회원 열쇠를 받습니다. 둘을 끊는 방식을 만들어 두었고, 아직 켜지 않았습니다.
- 옛 판의 명함
- 이전 판의 앱으로 올린 명함은 앱을 새로 받아 다시 올릴 때까지 모든 회원이 여는 회원 열쇠로 봉인되어 있습니다. 이전 판의 앱이 만든 QR·1촌 링크와 초대 요청 링크에는 이름이 실리고, QR·링크의 비밀은 찍을 때 서버로 갑니다.
- 초대 링크에 이름이 있습니다
- 지금은 초대 링크의 # 뒤에 초대한 사람의 이름이 있습니다(서버로는 가지 않습니다). 링크에서 이름을 빼고, 새 회원이 직접 쓴 명함을 초대한 사람이 보고 확인하는 방식을 만들어 두었고 아직 켜지 않았습니다.
- 링크로 봉한 명함
- QR·링크와 초대 요청에 실린 명함은 링크를 가진 사람이 엽니다. 링크와 서버 사본을 함께 가진 사람도 지우기 전까지(QR·링크는 만료되고 하루 뒤, 초대 요청은 끝나거나 7일 기한이 지나면) 열 수 있습니다.
- 건네받은 이름은 보낸 사람이 적은 것입니다
- 1촌 청·QR·대화로, 또는 사진 저장을 알리며 건넨 명함은 보낸 사람이 실어 보낸 것이라, 사람마다 다른 이름을 건넬 수 있습니다. 건넨 글에 실린 앞사람의 이름은 건넨 사람이 적은 것입니다. 앱은 그런 이름을 흐리게 보여 주고, 열린 명함의 이름이 있으면 그것을 먼저 씁니다.
- 열쇠는 앱이 실행되는 동안 메모리에 있습니다
- iPhone의 Secure Enclave는 우리가 쓰는 X25519·Ed25519를 다루지 못합니다. 그래서 개인 열쇠는 앱이 실행되는 동안 보안 코어의 메모리에 있습니다(Android도 같습니다). 폰 자체가 뚫리면 지키지 못합니다.
- 코어 밖의 계산
- PIN을 바꾸는 scrypt, 되찾기 숫자의 해시, 링크 열쇠의 HKDF와 링크로 봉하는 명함의 XChaCha20-Poly1305는 앱의 JavaScript(@noble 라이브러리)에서 계산합니다. 개인 열쇠가 아니어서 열쇠 경계 밖에 둡니다.
- 이미 나간 사본
- 서버 사본이나 백업이 이미 새어 나갔다면, 그 안의 옛 봉투와 옛 명함은 되돌릴 수 없습니다.
- 되찾기는 사람의 확인에 기댑니다
- 숫자는 조각이 전화 속 사람의 새 폰으로 가는지만 확인합니다. 전화 속 사람이 정말 나인지는 조각을 맡은 1촌이 판단합니다 — 목소리를 흉내 내면 속을 수 있습니다. 숫자 확인이 없는 이전 판의 앱을 쓰는 1촌은 털린 서버가 건넨 열쇠로 조각을 봉해 줄 수 있습니다.