TLS 1.3 핸드셰이크는 어떻게 1-RTT로 끝나는가

seonwoo_jung·2026년 5월 27일

1. 도입

HTTPS 트래픽을 캡처해서 보면 Client Hello 직후부터는 거의 모든 내용이 암호화돼서 와이어샤크로도 잘 보이지 않는다. TLS 1.2까지는 Certificate, Server Key Exchange 같은 메시지가 평문으로 흘러서 흐름을 눈으로 따라가기 쉬웠는데, TLS 1.3으로 넘어오면서 ServerHello 직후부터 핸드셰이크 메시지 자체가 암호화되도록 바뀌었다. 동작은 더 단단해졌지만, 처음 보면 "도대체 어디서 키가 생기길래 그 뒤가 암호화되지?"라는 의문이 생긴다.

이번 글에서는 RFC 8446 §2를 따라가면서 TLS 1.3 풀 핸드셰이크가 어떤 메시지를 주고받는지, 그리고 어느 시점에 어떤 키가 만들어지는지를 정리했다. 1-RTT 풀 핸드셰이크 흐름을 중심으로 보고, 세션 재개(PSK)와 0-RTT는 마지막에 짧게 다룬다.

2. 핵심 개념

TLS 1.3은 "키 교환 → 인증 → 응용 트래픽"의 3단계를 1-RTT 안에 끝내고, ServerHello 직후부터 핸드셰이크를 암호화한다.

RFC 8446 §2의 첫 문단을 풀어 쓰면 이렇게 정리된다.

  • 키 교환 단계: ClientHelloServerHello에서 (EC)DHE 또는 PSK로 공유 비밀을 만든다.
  • 서버 파라미터 단계: 서버가 EncryptedExtensions, (필요하면) CertificateRequest로 추가 파라미터를 보낸다.
  • 인증 단계: Certificate, CertificateVerify, Finished로 신원과 무결성을 증명한다.

핵심은 첫 번째 단계가 끝나는 순간(ServerHello까지) 이미 양쪽이 handshake traffic secret을 계산할 수 있다는 점이다. 그래서 그 뒤의 모든 핸드셰이크 메시지가 암호화된 채로 흐른다. TLS 1.2가 핸드셰이크 마지막에야 비로소 ChangeCipherSpec을 보내고 그제서야 암호화로 전환됐던 것과 달리, TLS 1.3은 키 교환이 끝나는 즉시 암호화로 들어간다.

3. 내부 동작 (1-RTT 풀 핸드셰이크)

RFC 8446 §2의 다이어그램을 텍스트로 옮기면 다음과 같다. {}는 handshake traffic key로, []는 application traffic key로 보호된다는 표기다.

Client                                           Server

ClientHello
  + key_share
  + signature_algorithms
  + supported_versions          ----->
                                                ServerHello
                                                  + key_share
                                        {EncryptedExtensions}
                                        {CertificateRequest*}
                                                {Certificate}
                                          {CertificateVerify}
                                                   {Finished}
                                <-----   [Application Data*]
{Finished}                      ----->
[Application Data]              <---->   [Application Data]

(* 는 상황에 따라 생략 가능. 다이어그램은 RFC 8446 §2의 1-RTT 핸드셰이크 그림을 간략화한 것이다.)

각 단계의 의미를 정리하면:

  1. ClientHello: 클라이언트가 지원하는 cipher_suites, supported_versions(TLS 1.3 명시), supported_groups(X25519, secp256r1 등), signature_algorithms, 그리고 key_share 확장을 보낸다. key_share에는 클라이언트가 추측한 그룹의 임시 공개키가 들어 있다.
  2. ServerHello: 서버가 그룹과 cipher_suite를 고르고, 자기 쪽 임시 공개키를 key_share로 응답한다. 이 시점에 양쪽 모두 (EC)DHE 공유 비밀을 가진다.
  3. 키 스케줄: 공유 비밀을 HKDF로 늘려서 client_handshake_traffic_secret, server_handshake_traffic_secret을 만든다. RFC 8446 §7.1의 키 스케줄 트리를 따라 early_secret → handshake_secret → master_secret 순으로 파생된다.
  4. EncryptedExtensions ~ Certificate ~ CertificateVerify: 서버가 자기 인증서를 보내고, 지금까지의 핸드셰이크 transcript에 서명을 붙여(CertificateVerify) 사설키 소유를 증명한다. transcript는 ClientHello부터 직전 메시지까지의 해시다.
  5. 서버 Finished: 서버가 핸드셰이크 transcript에 대한 HMAC을 보낸다. 이걸로 핸드셰이크 무결성이 닫힌다.
  6. 클라이언트 Finished: 클라이언트도 같은 방식으로 HMAC을 돌려보낸다.
  7. 그 뒤부터 application_traffic_secret으로 보호된 응용 데이터가 흐른다.

자주 헷갈리는 지점

  • "ServerHello도 평문 아닌가?" — 맞다. ServerHello까지는 평문이다. 그 직후의 EncryptedExtensions부터가 handshake key로 암호화된다. 그래서 와이어샤크에서도 EncryptedExtensions는 "Encrypted Handshake Message"로만 보인다.
  • SNI는 여전히 평문이다. ClientHello에 들어 있고, 이 단계에선 아직 키가 없으므로 암호화될 수 없다. SNI 자체를 숨기려면 별도 표준인 ECH(Encrypted Client Hello)가 필요하다고 알려져 있다.

4. 예시 / 엣지 케이스

서버가 클라이언트의 key_share 그룹을 지원하지 않으면, 1-RTT가 깨지고 한 번의 추가 왕복이 필요하다. 서버는 HelloRetryRequest(HRR)를 보내고 클라이언트는 새로운 그룹으로 ClientHello를 다시 보낸다.

ClientHello (key_share: X25519)   ----->
                                  <-----  HelloRetryRequest (group: secp256r1)
ClientHello (key_share: secp256r1) ----->
                                  <-----  ServerHello ...

HelloRetryRequest가 등장하면 TLS 1.3 풀 핸드셰이크라도 2-RTT가 된다는 뜻이다. 그래서 클라이언트는 보통 자주 쓰는 그룹의 key_share를 미리 채워 보낸다(예: 브라우저는 X25519를 우선).

세션 재개와 0-RTT

이전 연결에서 서버가 NewSessionTicket을 발급해 두면, 다음 연결에서 클라이언트는 ClientHellopre_shared_key 확장에 그 티켓을 실어 보낸다. (EC)DHE 없이 PSK만 쓸 수도 있고, PSK + (EC)DHE를 함께 써서 forward secrecy를 유지할 수도 있다.

early_data 확장을 함께 보내면 클라이언트가 ClientHello 다음에 바로 응용 데이터를 보낼 수 있는데(0-RTT), RFC 8446 §2.3의 경고대로 0-RTT 데이터는 재전송 공격에 취약하다. 그래서 0-RTT는 멱등한 요청에만 허용해야 한다고 알려져 있다.

5. 정리

TLS 1.3 풀 핸드셰이크는 "ClientHello + key_share → ServerHello + key_share"에서 공유 비밀이 만들어지고, 그 직후부터 모든 핸드셰이크 메시지가 암호화된다. 인증과 무결성은 CertificateVerifyFinished로 닫힌다.

핵심을 한 줄로 줄이면, 키 교환이 핸드셰이크 앞단으로 당겨졌기 때문에 1-RTT와 핸드셰이크 암호화가 동시에 가능해졌다는 것이다.

더 파고들어 볼 만한 주제는 두 가지로 정리했다.

  • 키 스케줄(HKDF 트리): early_secret → handshake_secret → master_secret 파생 과정을 RFC 8446 §7.1 그림으로 따라가 보기.
  • PSK 재개와 0-RTT 보안 모델: 재전송 공격이 왜 본질적으로 막을 수 없는지, 어떤 요청에 0-RTT를 열어도 안전한지.

참고 자료

0개의 댓글