seQure Lattice

양자컴퓨터가 나와도
열리지 않는 대화

NIST 양자내성 표준을 실제 메신저에 넣어본 안드로이드 앱. 서버는 암호문만 보관하고, 복호화 키는 각자의 기기를 떠나지 않습니다.

주어진 좌표 격자 위에 있지 않다 문제 — 가장 가까운 격자점은 어느 것인가
차원이 768까지 올라가면 이 질문에 빠르게 답하는 방법이 알려져 있지 않습니다. 양자컴퓨터에게도 마찬가지입니다. 이 앱의 암호는 그 위에 서 있습니다.

지금 오간 대화가 15년 뒤에 열린다면

오늘 웹과 메신저가 쓰는 암호는 큰 수를 소인수분해하기 어렵다는 성질에 기대고 있습니다. 양자컴퓨터는 바로 그 계산을 빠르게 합니다.

당장 양자컴퓨터가 없어도 문제입니다. 지금 오가는 암호문을 저장해뒀다가 나중에 푸는 방식이 가능하기 때문입니다. 오래 비밀이어야 하는 대화일수록 지금부터 대비해야 합니다.

NIST가 2024년에 대체 표준을 확정했습니다. 다만 그 표준을 실제 메신저에 어떻게 넣는가는 별개의 문제입니다. 암호 자체보다 키를 언제 만들고 언제 버리고 어떻게 나눠주느냐가 훨씬 어렵습니다. 이 프로젝트는 그 부분을 직접 해본 기록입니다.

구조

같은 앱을 두 갈래로 만들었습니다

나머지 코드는 완전히 같고, 키 교환 계층만 다릅니다. 두 브랜치의 차이는 암호 파일 열 개뿐입니다.

ver1-full-security

라이브러리를 쓴다

BouncyCastle의 ML-KEM-768을 그대로 사용합니다. 부채널 대응과 상수 시간 보장이 들어간 검증된 구현이고, 실제로 쓸 거라면 이쪽입니다.

ver2-kem-from-paper

논문을 보고 만든다

CRYSTALS-Kyber 논문의 Algorithm 1~5를 라이브러리 없이 구현했습니다. 다항식 곱셈은 NTT 대신 학교식으로 두어 무엇을 하는지 보이게 했습니다.

표준을 쓸 줄 아는 것이해한 것은 다르다고 봤습니다. 직접 만들어보니 왜 그렇게 설계됐는지 — 부동소수점을 쓰지 않는 이유, 복호화 실패를 그냥 실패로 알리지 않는 이유 — 가 보였고, 라이브러리를 쓸 때도 무엇을 믿고 쓰는 것인지 알게 됐습니다.

설계

키를 다루는 방식

1:1 대화
메시지마다 키를 새로 만들고 이전 키는 지웁니다. 대화 방향이 바뀔 때마다 새 키 교환을 섞어, 기기가 한 번 털려도 그 이후 대화는 다시 안전해집니다.
그룹 대화
1:1 방식은 두 사람의 왕복이 있어야 성립해 N명에게는 쓸 수 없습니다. 각자 자기 발신 사슬을 만들어 나눠주고, 멤버가 드나들 때·24시간마다·500건마다 사슬을 새로 만들어 노출 구간을 끊습니다.
상대 확인
서버가 알려주는 공개키를 믿지 않습니다. 처음 받은 값의 지문을 기기에 저장해두고 달라지면 멈춥니다. 처음부터 가짜인 경우는 두 사람이 서버를 거치지 않는 경로로 안전번호를 대조해 잡습니다.
서버의 역할
암호문을 잠시 맡아 전달하고, 배달이 확인되면 지웁니다. 대화 기록의 정본은 각 기기의 암호화된 로컬 DB이며 계정마다 다른 열쇠로 잠깁니다.
쓰임알고리즘비고
키 교환ML-KEM-768FIPS 203
전자서명ML-DSA-65FIPS 204
메시지 암호화AES-256-GCM인증 태그 128비트
키 유도HKDF-SHA256
기기 저장소SQLCipher계정별 패스프레이즈
전송 구간TLS 1.3Let's Encrypt
규모

숫자

8,145클라이언트 (Kotlin)
2,178서버 (Java)
579직접 구현한 KEM
2브랜치

안드로이드는 Kotlin과 Jetpack Compose, 서버는 Spring Boot와 PostgreSQL을 씁니다. 실시간 전달은 WebSocket 위의 STOMP를 쓰고, AWS EC2에 Nginx를 앞에 두어 배포했습니다.

한계

하지 않은 것

보안 제품은 무엇을 했는지보다 무엇을 하지 않았는지가 더 중요합니다. 알고 있는 한계를 적어둡니다.

외부 보안 감사를 받지 않았습니다. 알고리즘은 NIST 표준을 쓰지만, 그것을 조립한 방식은 검증받은 적이 없습니다.
처음 연결할 때는 여전히 속을 수 있습니다. 서버가 첫 조회부터 가짜 키를 주면, 두 사람이 안전번호를 실제로 대조하기 전까지는 알 수 없습니다. 이를 막으려면 발급된 키를 공개 기록에 남기는 키 투명성이 필요한데 구현하지 않았습니다.
서버는 누가 누구에게 보냈는지 압니다. 내용은 모르지만 관계와 시각은 드러납니다.
직접 구현한 KEM은 학습용입니다. 연산 시간이 비밀값에 따라 달라지지 않게 하는 처리를 하지 않아, 부채널 공격에 노출됩니다. 실제 경로에는 라이브러리를 쓰는 ver1을 권합니다.
학습과 포트폴리오를 목적으로 만든 프로젝트입니다. 실제로 비밀이 지켜져야 하는 대화에는 Signal처럼 오랜 기간 감사받은 도구를 쓰시기 바랍니다.
코드

저장소

두 갈래의 차이는 git diff 로 바로 확인할 수 있습니다.