[제45회] DB 서버가 필요 없는 기적! 암호화된 비밀번호와 카운터를 내장한 '자체 완결형 스테이트리스 ID'
요약
본 글은 DB 서버 없이 방 입장을 검증하는 '자체 완결형 스테이트리스 ID' 설계 과정을 다룹니다. JWT나 Kerberos의 사상을 응축한 이 토큰은 정책, 카운터, 비밀번호 해시(HMAC), 서명 등 모든 검증 데이터를 URL 자체에 암호화하여 패킹합니다. 이를 통해 서버는 DB 쿼리 없이 메모리 계산만으로 합격 여부를 판정하는 완전 스테이트리스 아키텍처를 구현했습니다.
핵심 포인트
- DB 의존성을 제거하고 P2P 환경을 지향하며, 모든 검증 데이터를 URL에 내장합니다.
- ID는 정책 식별자, 난독화 카운터, 비밀번호 토큰(HMAC), 서명으로 구성됩니다.
- 비밀번호 평문 대신 HMAC-SHA256 해시의 상위 4자리만 포함하여 보안성을 높였습니다.
- 최신 버전에서는 순번 추측이 불가능하도록 CSPRNG와 HMAC 서명을 사용합니다.
안녕하세요! 2채널 호환 게시판 시스템인 "츠우바이채널(つゔぁいちゃんねる, 2ch.biz)" 개발 기록 제45회입니다.
지난번(제44회)에서는 빈 비밀번호 방식의 방 탈취를 막기 위해 정책과 비밀번호 해시 값을 기록하는 경량 레지스트리 테이블을 구축했습니다.
보안은 완벽해졌고, 일단락된 것처럼 보였습니다. 하지만 저희 마음속에는 '떨쳐낼 수 없는 의문'이 하나 남아 있었습니다.
"우리는 '서버에 로그를 전혀 남기지 않는 완전 P2P'를 표방하고 있는 것 아닌가?"
"그런데 방의 비밀번호나 정책을 검증하기 위해서만 서버의 SQLite 데이터베이스에 레코드를 계속 저장하는 것이 정말 아름다운 아키텍처라고 할 수 있을까?"
"데이터베이스에 방 레코드 한 줄도 쓰지 않으면서",
"그래도 URL과 비밀번호가 올바른지 서버가 완벽하게 검증할 수는 없을까?"
본고에서는 웹 표준인 JWT(JSON Web Token)나 Kerberos 인증의 사상을 단 14글자의 URL 문자열에 응축한, '암호화 자체 완결형 스테이트리스 ID (Self-contained Room Token)' 의 탄생 비화를 설명합니다!
통상적으로 비밀번호 인증이라고 하면, 데이터베이스에 저장된 해시 값과 대조하는 것이 웹 개발의 상식입니다.
-- 일반적인 접근 방식 (DB 필요)
SELECT pass_hash FROM rooms WHERE room_id = 'xxx';
하지만 만약 서버의 데이터베이스에 테이블을 만들지 않는다면, 서버는 어떻게 '이 방이 비밀번호 방식인지', '사용자가 입력한 합문이 맞는지'를 알 수 있을까요?
정답은 '필요한 검증 데이터를 모두 암호화하고 서명하여, 루미 ID(URL) 그 자체 안에 채워 넣는 것' 이었습니다.
저희가 설계한 자체 완결형 방 ID는 겉보기에는 평범한 URL 슬러그(영문/숫자 12~14자)이지만, 내부에는 극도로 정밀한 데이터 구조가 패킹되어 있습니다.
$$ ext{ID 형식}: ext{room_} + ext{<정책>} + ext{<난독화 카운터>} + ext{<비밀번호 토큰>} + ext{<서명>}$$
(예: room_p_7k9a2f_4m8x9p_c2)
-
정책 식별자 (1글자):
o: 오픈 방식 (누구나 자유롭게 입장 가능)
p: 비밀번호 방식 (합문이 필요함)
i: 초대 승인 방식 (호스트의 승인이 필요함) -
난독화 카운터 $N$ (4~6글자): 제43회에서 구현한 솔트 XOR 처리된 발행 순번(Base36).
-
비밀번호 검증 토큰 (4글자): 비밀번호 방식의 경우에만, 서버의 비밀키와 카운터 솔트를 사용하여 계산한 $ ext{HMAC-SHA256}( ext{passcode}, ext{salt})$ 의 상위 4자리.
- ※비밀번호 평문은 일절 포함되지 않으며, 되돌릴 수 없는 해시 검증 코드만 포함합니다. 오픈 방식의 경우 무작위 노이즈를 채워 넣습니다.
-
변조 방지 서명/체크섬 (2글자): 상기 모든 문자열에 대해 서버 비밀키로 계산한 서명. URL이 1글자라도 변경되었다면 디코드 시 즉시 감지됩니다.
사용자가 초대 URL을 클릭하거나 '기존 방 참여'를 입력할 때, 시그널링 서버는 어떠한 DB 쿼리도 발생시키지 않고, 메모리 상의 계산만으로 합격 여부를 판정합니다.
[!NOTE]
본고에서는 '순번 카운터를 Base36 마스크하여 자체 완결시키는 접근 방식'을 설명했습니다. 이후 보안 강화에 따라, 현재는 순번 카운터 자체를 완전히 제거하고, '암호학적 난수 Nonce (CSPRNG) + 서버 비밀키 HMAC 서명' 을 순수한 11글자의 Base36으로 응축한, 순번 추측이 원리적으로 불가능한 스테이트리스 토큰 체계로 전환했습니다.
const SERVER_SECRET = crypto.randomBytes(32); // 서버 비밀키
const DUMMY_MASK = 0x2a7b; // 설명용 더미 마스크 값
/**
...
/**
* DB를 일절 사용하지 않는 자체 완결형 ID 검증
*/
...
전용 테스트 스크립트를 실행하여, 데이터베이스에 테이블이 하나도 존재하지 않은 상태에서 검증을 진행했습니다.
| 테스트 항목 | 조건 | 기대 결과 | 판정 |
|---|---|---|---|
| 무작위 ID | 존재하지 않는 무작위 문자열 | 서명 불일치로 INVALID_ROOM_ID 반환 | |
| ✅ PASS | |||
| URL 1글자 변조 | 토큰의 마지막 글자를 변경 | 서명 불일치로 즉시 감지 | |
| ✅ PASS | |||
| 잘못된 비밀번호 | 올바른 ID + wrong_pass | ||
INVALID_PASSWORD로 거부 | |||
| ✅ PASS | |||
| 올바른 비밀번호 | 올바른 ID + mypass123 | ||
| 방이 재개되고, 입장 성공! | ✅ PASS | ||
| DB 테이블 레코드 수 | SQLite의 SELECT count(*) | ||
| 0 행 (완전 제로 스토리지) | ✅ PASS |
서버 데이터베이스에 방 기록을 단 한 줄도 남기지 않으면서, 강력한 비밀번호 검증과 빈방 재개 기능을 실현하는 '자체 완결형 스테이트리스 ID'.
암호학적 해시 함수(HMAC)와 비트 패킹(Bit Packing)을 활용한 이 설계 덕분에, 2ch 채널의 P2P 채팅은 진정한 '제로 스토리지 & 완전 스테이트리스' 라는 궁극적인 경지에 도달했습니다.
하지만 기술적으로 아무리 정교한 암호 토큰을 만들더라도, 사용자에게 '사용하기 불편하면' 의미가 없습니다.
'방을 만들 때 이렇게 길고 복잡한 토큰을 수동으로 복사/붙여넣기 해야 하는 건가?'
'버튼을 한 번 클릭하는 것만으로 순식간에 방이 생성되고 초대 URL이 복사되는 사용자 친화적인 UI가 필요하다!'
실제 서비스: https://2ch.biz/
AI 자동 생성 콘텐츠
본 콘텐츠는 Qiita AI의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기