뉴스 · Blackbear의 보안
우리가 읽을 수 없는 것
Blackbear는 로컬 우선이에요. 모든 기능이 내 기기에서 작동하고, 기본적으로 데이터는 서버를 거치지 않고 그곳에만 있어요.
클라우드 동기화를 켜면, 우리 서버가 내 기기들 사이, 그리고 내 사람들에게 데이터를 실어 나르기 시작해요. 이 시리즈는 그것이 내 개인정보에 어떤 의미인지 설명해요. 1부는 암호화, 즉 무엇이 내 기기를 떠나고 왜 우리가 그것을 읽을 수 없는지를 다뤘요. 2부는 우리 서버가 실제로 가지고 있는 메타데이터를 솔직하게 정리한 목록이고, 3부는 공유와 협업을 다뤘요.
아래 내용은 모두 계획이 아니라 실제로 출시된 코드에 대한 설명이에요. 알고리즘이나 수치를 언급하는 곳은 모두 오늘 프로덕션에서 실제로 돌아가는 것이에요.
암호화는 내 기기에서 일어나요
동기화하는 모든 변경 사항은 네트워크에 닿기 전에 암호화돼요. 노트, 문서, 할 일, 일정, 저널 항목, 스프레드시트, 캔버스, 오디오, 버전 기록까지. 하나하나가 인증된 암호인 XChaCha20-Poly1305로, 보호되지 않은 채로는 절대 내 기기를 떠나지 않는 키를 써서 봉인돼요. 우리 서버는 암호문을 받고, 암호문을 저장해요.
이미지와 오디오 파일도 똑같아요. 내 기기에서 암호화되고, 안을 볼 수 없는 덩어리로 업로드돼요.
이 모든 일을 하는 코드는 RustCrypto 생태계 위에 만들어져 WebAssembly로 컴파일된 하나의 Rust 구현이에요. 앱과 Blackbear CLI가 바이트 하나까지 똑같이 이 코드를 쓰고, 두 번째 구현이 모르는 사이 어긋나지 않도록 여러 언어에 걸친 테스트 데이터로 검증해요.
무엇을 지키고, 무엇은 지키지 못하는지
암호화는 특정한 도구이지 만능 방패가 아니에요. 자세한 이야기에 앞서, 그 경계부터 말할게요.
이 설계가 지켜 주는 것:
- 우리 서버나 데이터베이스가 수동적으로 침해당해 저장된 데이터가 도난당하는 경우. 유출되는 것은 암호문과 포장된 키뿐이에요.
- 우리. 출시된 우리 프로덕션 시스템은 내 콘텐츠 키나 평문을 절대 받지 않아요. 그래서 내 콘텐츠를 내놓으라는 법적 요구가 있어도 나오는 것은 우리가 실제로 가진 것, 즉 메타데이터(2부)와 봉인된 덩어리뿐이에요.
지켜 주지 못하는 것:
- 침해당했거나 도난당했거나 잠금이 풀린 기기. 키는 내 기기에 있고, 기기의 보안이 다른 모든 것을 떠받치는 바닥이에요.
- 내 암호 문구나 복구 단어 12개를 알고 있는 사람.
- 내가 공유한 것을 권한 있는 수신자가 어떻게 하는지. 공유는 수학만이 아니라 사람에 대한 신뢰예요.
- 트래픽 메타데이터: 누가, 언제, 얼마나. 그것이 2부의 주제 전체예요.
- 앱 그 자체. 웹에서는 앱이 로드할 때마다 우리 서버에서 오고, 데스크톱에서는 서명된 릴리스로 와요. 누가 배포하든, 악의적인 앱 앞에서 버티는 암호화 방식은 없어요.
- 내가 일부러 데이터를 암호화하지 않고 내보내는 순간: Pax에게 클라우드 모델을 쓰게 하거나, 외부 캘린더를 연결하거나, 파일을 내보내거나, 초대를 이메일로 보낼 때예요. 이런 데이터는 내가 고른 상대에게 의도적으로 전달되고, 우리의 암호화가 아니라 그 상대의 정책을 따라요.
키의 계층 구조
내 암호 문구는 절대 내 기기를 떠나지 않아요. 모든 것이 여기서 출발해요.
- 암호 문구는 메모리 하드 키 유도 함수인 Argon2id를 거쳐요. 메모리 64MiB, 3회 반복, 레인 4개로 설정돼 있고, 그 결과가 계정 키(Account Key)예요.
- 계정 키는 보관함 키(Vault Key)를 포장해요. 보관함 키는 내 기기에서 만든 무작위 256비트 키로, 실제로 데이터를 암호화하는 키예요.
- 우리 서버는 포장된 보관함 키, 솔트, 설정값을 저장해요. 포장된 키 자체가 암호문이에요. 내 암호 문구가 없으면 의미 없는 잡음일 뿐이에요.
서버는 포장된 사본만 저장해요. 암호문을 지키는 암호문이에요.
왜 이렇게 한 단계를 거칠까요? 그래야 내 삶 전체를 다시 암호화하지 않고도 암호 문구를 바꿀 수 있고, 정문을 약하게 만들지 않고도 복구 수단을 둘 수 있기 때문이에요.
분명히 적어 둘 주의사항이 있어요. Argon2id는 추측을 비싸게 만들 뿐, 약한 암호 문구를 강하게 만들지는 못해요. 포장된 보관함 키는 그것을 포장한 암호 문구만큼만 강해요. 긴 암호 문구를 고르세요.
복구는 단어 열두 개
동기화를 설정하면 Blackbear가 복구 문구를 줘요. 128비트의 엔트로피를 담은 단어 열두 개(BIP39)예요. 이 단어들에서 독립적인 키가 나오고, 그 키가 보관함 키를 한 번 더 포장해요.
암호 문구를 잊었나요? 단어 열두 개로 다시 들어올 수 있어요. 둘 다 잊었다면요? 동기화된 데이터는 사라져요. 우리는 그것을 재설정할 수 없고, 어떤 고객 지원 요청으로도 그렇게 할 수 없어요. 우리 쪽에는 재설정할 것이 아무것도 없기 때문이에요. 이것은 고객 지원의 빈틈이 아니라, 설계가 제대로 지켜지고 있다는 뜻이에요.
기기 추가하기
새 기기에서 암호 문구를 입력하면, 기기 안에서 계정 키를 만들고 우리 서버가 가진 포장된 사본에서 보관함 키를 꺼내요. 서버는 암호 문구도, 그 결과도 절대 보지 못해요.
잠금이 풀린 기존 기기가 키를 직접 넘겨줄 수도 있어요. 기존 기기는 보관함 키를 새 기기의 X25519 공개 키로 봉인해, 쓰였든 안 쓰였든 5분 뒤에 스스로 사라지는 전송 슬롯에 넣어 두어요. 여기에는 솔직하게 말해야 할 경계가 하나 있어요. 기존 기기는 로그인된 계정을 통해 새 기기의 공개 키를 알게 되므로, 이 보조 방식은 내가 고른 기기의 올바른 키를 우리 서버가 전달한다고 믿어야 해요. 암호 문구 방식에는 그런 신뢰가 필요 없어요.
피싱 사기꾼이 바로 이런 용어를 빌려 쓰기 때문에, 분명히 적어 둘 규칙이 있어요. 우리는 절대 암호 문구나 복구 단어를 묻지 않고, 기기 전송을 승인해 달라고 요청하지도 않아요. 그런 화면은 내가 직접 연 앱 안에만 나타나요. 그 밖에서 묻는다면 그건 공격이에요.
메시지
메시지는 동기화와 별개인 자체 키를 써요. 계정마다 X25519 메시지 신원이 있고, 모든 메시지는 메시지마다 새로 만든 솔트에서 HKDF-SHA256으로 고유한 키를 만들어요. 메시지는 봉인된 봉투로 전달돼요. 서버는 그것을 전달할 뿐 열 수 없어요. 봉투 겉면에서 무엇이 드러나는지는 2부에서 다뤘요.
설계에 대해 하나 분명히 말해 둘게요. 메시지마다 고유한 키를 쓰는 것은 일부 메신저가 쓰는 래칫 방식과 다르요. 기기의 장기 메시지 키가 언젠가 유출된다면, 그 키로 봉인된 메시지는 위험해져요. 메시지별 키는 피해 범위를 줄여 주지만 순방향 비밀성은 아니에요. 그 차이를 알고 계시길 바라요.
내용의 지문을 남기지 않는 버전 기록
버전 기록은 암호화된 스냅샷으로 동기화되고, 내 보관함 안에서만 유효한 ID로 주소가 매겨져요. 나와 모르는 사람이 똑같은 파일을 저장해도 서버에는 서로 관계없는 두 ID가 보여요. 계정을 넘어 똑같은 내용을 대조할 수 없다는 뜻이에요. 크기와 시점은 다른 모든 동기화 트래픽처럼 그대로 보여요. 그건 메타데이터이고, 그 주제는 2부에서 다뤘요.
드러내지 않고 증명하기
되돌릴 수 없는 계정 작업에는 보관함 증명이 필요해요. 보관함 키에서 만든 Ed25519 키로 한 서명이에요. 보관함을 어디로도 보내지 않고 내가 보관함을 가지고 있다는 것을 증명하는 거예요. 그리고 메모리에서 할 일을 마친 키는 0으로 지워져요.
여기에 특이한 건 없어요. XChaCha20-Poly1305, Argon2id, X25519, Ed25519, HKDF-SHA256: 표준적이고, 평범하고, 철저히 검토된 기본 요소들이에요. 흥미로운 부분은 이것들을 빠짐없이 적용하고, 이것들이 지켜 주지 못하는 것을 소리 내어 말하는 규율이에요. 이 시리즈의 나머지는 바로 그 규율에 대한 이야기예요.
더 읽을거리
- Local-first software — 이 철학의 바탕이 된 Ink & Switch의 에세이
- RFC 9106 — Argon2 명세
- XChaCha20-Poly1305 — IETF 명세 초안
- RFC 7748 — X25519의 바탕이 된 타원 곡선
- RFC 8032 — Ed25519 서명
- The Password Hashing Competition — Argon2가 우승한 대회
Blackbear의 보안
- 1. 우리가 읽을 수 없는 것 (지금 읽는 글)
- 2. 우리 서버가 볼 수 있는 것
- 3. 공유해도 봉인은 그대로