TLS(Transport Layer Security) 핸드셰이크는 매 연결마다 대칭키를 새로 합의하고 서버 인증서를 검증한다. 왕복과 비대칭 연산이 따르는 비싼 과정이다. 실무에서 TLS를 다루다 보면 이 기본 동작만으로 부족한 지점이 세 군데 드러난다. 같은 서버에 반복 접속할 때의 핸드셰이크 비용, 서버만 인증하고 클라이언트는 익명으로 두는 신뢰 범위, 그리고 유효기간이 있는 인증서의 갱신 부담이다.
세 가지 모두 프로토콜 자체를 바꾸지 않는다. 기본 핸드셰이크를 전제로 그 동작을 재개하고, 확장하고, 자동화하는 실무 계층이다.
세션 재개
핸드셰이크의 핵심 비용은 키 합의다. 비대칭 연산과 왕복이 여기 들어간다. 그런데 같은 클라이언트가 조금 전 접속했던 서버에 다시 붙는 경우가 흔하다. 이때 처음부터 키를 다시 합의하는 것은 낭비다. 세션 재개(Session Resumption)는 이전 세션에서 합의한 비밀을 재사용해 핸드셰이크를 축약한다.
방식은 두 가지로 나뉜다.
Session ID는 서버가 세션 상태를 저장하는 방식이다. 최초 핸드셰이크에서 서버가 세션에 ID를 부여하고, 자신의 메모리에 그 세션의 키와 상태를 보관한다. 클라이언트가 재접속하며 이 ID를 제시하면, 서버는 저장된 상태를 찾아 키 합의를 건너뛴다. 단점은 서버가 모든 활성 세션의 상태를 들고 있어야 한다는 점이다. 세션이 많아지면 메모리 부담이 커지고, 로드밸런서 뒤 여러 서버가 상태를 공유해야 하는 문제도 생긴다.
Session Ticket은 이 상태 저장을 클라이언트에게 맡긴다. 서버는 세션 상태를 자신만 아는 키로 암호화해 티켓으로 만들고 클라이언트에게 건넨다. 재접속 시 클라이언트가 이 티켓을 제시하면, 서버는 복호화해 세션 상태를 복원한다. 서버는 티켓 암호화 키 하나만 관리하면 되므로, 세션 수와 무관하게 상태를 저장하지 않는다.
TLS 1.3은 이 둘을 PSK(Pre-Shared Key, 사전 공유 키)로 통합했다. 이전 핸드셰이크에서 파생한 비밀을 다음 연결의 사전 공유 키로 쓴다.
sequenceDiagram
participant C as 클라이언트
participant S as 서버
Note over C,S: 최초 핸드셰이크
S->>C: NewSessionTicket (세션 티켓 발급)
Note over C,S: ── 이후 재접속 ──
C->>S: ClientHello + 티켓 / PSK
S->>C: ServerHello (키 합의 생략)
Note over C,S: 축약된 핸드셰이크로 재개
0-RTT
세션 재개는 왕복을 줄이지만 없애지는 못한다. TLS 1.3은 여기서 한 걸음 더 간다. PSK를 가진 클라이언트라면 재접속의 첫 패킷에 애플리케이션 데이터를 실어 보낼 수 있다. 왕복(RTT, Round-Trip Time)을 하나도 기다리지 않는다는 뜻에서 0-RTT, 또는 early data라 부른다. 재접속 지연이 사실상 사라진다.
대가가 있다. early data는 핸드셰이크가 끝나기 전에 전송되므로 일반 TLS 데이터가 갖는 보호를 온전히 받지 못한다. 특히 재전송 공격(Replay)에 취약하다. 공격자가 early data 패킷을 캡처해 서버에 그대로 다시 보내면, 서버는 이를 정상 요청으로 중복 처리할 수 있다. 결제나 상태 변경 요청이 두 번 실행되면 문제가 된다.
그래서 0-RTT는 취급에 제약을 둔다. early data에는 멱등(Idempotent)한 요청만 허용하는 것이 원칙이다. 같은 요청이 두 번 처리돼도 결과가 달라지지 않는 조회성 요청, 예컨대 GET이다. 서버 측에서는 티켓을 한 번만 쓰도록 기록하거나 짧은 시간 윈도우를 두어 재전송을 걸러내는 anti-replay 방어를 둔다. 상태를 바꾸는 요청은 0-RTT로 보내지 않는 편이 안전하다.
mTLS
지금까지의 TLS는 서버만 인증한다. 클라이언트는 서버의 인증서를 검증하지만, 서버는 클라이언트가 누구인지 확인하지 않는다. 공개 웹에서는 이것으로 충분하다. 사용자는 로그인 같은 애플리케이션 계층에서 따로 인증한다.
하지만 통제된 환경에서는 연결 자체를 인증하고 싶은 경우가 있다. mTLS(mutual TLS, 상호 TLS)는 양방향 인증이다. 핸드셰이크에서 서버가 CertificateRequest로 클라이언트에게도 인증서를 요구하고, 클라이언트가 자신의 인증서와 함께 개인키로 만든 서명을 제시한다. 인증서는 공개 정보라 복사할 수 있으므로, 이 서명이 인증서의 실제 소유자임을 증명한다. 양쪽이 서로의 인증서와 서명을 검증해야 연결이 성립한다.
sequenceDiagram
participant C as 클라이언트
participant S as 서버
C->>S: ClientHello
S->>C: ServerHello + Certificate
S->>C: CertificateRequest
C->>S: Certificate + CertificateVerify (인증서 + 서명)
Note over C,S: 양쪽 인증서와 서명 상호 검증
Note over C,S: 연결 성립
주로 마이크로서비스 간 내부 통신이나 서비스 메시에서 쓰인다. 서비스 A가 서비스 B를 호출할 때, 상대가 신뢰할 수 있는 서비스임을 인증서로 확인한다. 애플리케이션 코드가 아니라 연결 수립 단계에서 신원이 검증되므로, 내부망에 침입한 요청을 네트워크 계층에서 걸러낼 수 있다.
ACME
인증서에는 유효기간이 있다. 만료되면 갱신해야 하고, 만료된 인증서를 방치하면 접속이 끊긴다. 인증서를 손으로 발급하고 갱신하던 시절에는 만료를 놓쳐 서비스가 중단되는 사고가 잦았다.
ACME(Automatic Certificate Management Environment)는 이 발급과 갱신을 자동화하는 프로토콜이다. Let’s Encrypt가 이를 통해 무료 인증서를 발급하면서 널리 퍼졌다. certbot 같은 클라이언트가 CA(Certificate Authority, 인증 기관)와 ACME로 통신해 인증서를 자동으로 받아온다.
핵심은 도메인 소유 검증이다. CA는 인증서를 요청한 쪽이 정말 그 도메인을 통제하는지 확인해야 한다. ACME는 주로 두 가지 챌린지를 쓴다.
HTTP-01은 CA가 지정한 토큰을 도메인의 특정 웹 경로에 두게 한다. CA가 그 경로에 접속해 토큰을 확인하면 도메인 통제가 증명된다.
DNS-01은 토큰을 도메인의 DNS TXT 레코드로 등록하게 한다. CA가 DNS를 조회해 확인한다. 웹 서버 없이도 검증할 수 있고, 와일드카드 인증서 발급에 쓰인다.
flowchart TD
A["ACME 클라이언트
certbot 등"] -->|인증서 요청| CA["CA
Let's Encrypt"]
CA -->|챌린지 발급| A
A -->|"토큰 배치
HTTP-01: 웹 경로
DNS-01: TXT 레코드"| D["도메인"]
CA -.검증.-> D
CA -->|인증서 발급| A
검증이 끝나면 CA가 인증서를 발급한다. 클라이언트는 만료 전에 이 과정을 자동으로 반복해 갱신한다. 사람이 개입하지 않으므로 만료로 인한 중단이 사라진다.
마무리
핸드셰이크가 안전한 채널을 세우는 기본이라면, 세션 재개는 그 반복 비용을, mTLS는 인증이 미치는 신뢰 범위를, ACME는 인증서의 운영 부담을 다룬다. 셋 다 TLS 프로토콜을 바꾸지 않고 그 동작을 재개하고 확장하고 자동화하는 실무 계층이다. 기본 핸드셰이크를 이해하고 나면, 실무에서 마주치는 TLS 설정 대부분은 이 세 가지의 변주로 읽힌다.
참고
- TLS 핸드셰이크와 인증서 — 이 글이 전제하는 기본 핸드셰이크와 인증서 신뢰 모델
- HTTP/1.1과 HTTP/2 — 0-RTT를 전송 계층에서 활용하는 QUIC/HTTP3의 맥락