HTTPS는 자물쇠만 뜨면 끝일 것 같죠? 사실 그 직전에는 꽤 촘촘한 준비 대화가 먼저 오가요.TLS, SSL, 인증서는 뭐가 다를까요?에서는 브라우저가 진짜 서버인지 확인하고 보호된 통로를 준비한다는 큰 그림을 먼저 봤어요. 그리고 End-to-End Request Debugging에서는 연결은 열렸는데 TLS에서 시간이 쓰일 수도 있다는 감각도 붙여봤죠. 근데 여기서 이런 궁금증이 딱 생겨요.
“좋아요, TLS가 보호된 통로를 준비한다는 건 알겠어요. 근데 TLS 1.3에서는 그 준비가 정확히 어떤 순서로 오가죠?”근데 이 글이 필요한 이유는 단순히 메시지 이름을 외우기 위해서가 아니에요.
- 왜 TLS 1.3은 1-RTT 감각으로 설명되는지
- 왜
ServerHello뒤부터는 장면이 갑자기 덜 보이기 시작하는지 - 왜
Certificate하나로 끝나지 않고CertificateVerify,Finished까지 여러 확인 단계가 필요한지
ClientHello 부터 Finished 까지 각 메시지가 프로토콜 안에서 무슨 역할을 맡는지 같이 해부해볼게요. RFC 기준으로는 RFC 8446 2장과 4장 의 흐름을 바탕으로 볼게요.
여기서는 TLS 1.3의 기본적인 인증서 기반 1-RTT 핸드셰이크를 큰 뼈대로 볼게요. 인증서 체인 검증의 세부 정책, ECH, QUIC, 기업용 TLS 설정표 같은 주제까지는 지금 다 열지 않아요. 또 HKDF 수식 자체를 암호학 교과서처럼 풀기보다, “왜 이 메시지가 여기서 필요할까” 에 집중할 거예요.반대로 “좋아요, 이 구조는 알겠어요. 근데 실제 줄을 보면 어디부터 읽죠?” 같은 질문은 TLS 핸드셰이크는 실제로 어떻게 한 단계씩 진행될까요?에서 장면 읽기 감각으로 따로 이어볼게요.
그래서 TLS 1.3 핸드셰이크는 한마디로 뭐예요?
TLS 1.3 핸드셰이크는 단순히 “암호화 켜기” 버튼을 누르는 일이 아니라, 규칙 협상 → 암호화 경계 세우기 → 신원 확인 → 마지막 무결성 확인 순서로 진행되는 준비 절차에 더 가까워요. 이 절차는 TCP가 이미 열린 뒤에 올라와서,- 어떤 보호 조합으로 갈지 맞추고,
- 서버가 진짜 그 서버인지 확인하고,
- 그 확인 대화가 중간에 안 바뀌었는지 닫은 다음,
- 그제야 HTTP 같은 application data 를 안전하게 올리게 해줘요.
그리고 TLS 1.3이 이런 모양으로 정리된 데에는 배경이 있어요. 예전보다 왕복을 줄이고, 암호화 경계를 더 빨리 세우고, 중간에서 평문으로 보이는 협상 정보를 줄이려는 방향으로 크게 움직였거든요. 그래서 이 글은 메시지 목록만 보는 글이 아니라, 왜 TLS 1.3이 이렇게 재배치됐는지까지 같이 읽는 글이에요.
TLS 1.3 전체 흐름은 이렇게 생겨요
가장 많이 보는 인증서 기반 full handshake 를 한 장으로 그리면 이래요. 여기서는 방향별 경계를 따로 표시했어요. 서버는 자기Finished를 보낸 뒤 application data를 보낼 수 있고, 클라이언트도 자기 Finished를 보낸 뒤부터 보낼 수 있어요. 흔한 HTTP 장면에서는 클라이언트 Finished 다음에 첫 요청이 이어져서 양쪽 Finished 뒤에 HTTP가 시작되는 모양으로 자주 보일 뿐이에요.
이 그림에서 가장 먼저 잡아야 할 핵심은 세 가지예요.
- 앞부분 두 메시지(
ClientHello,ServerHello)가 암호화 경계를 세운다는 점 ServerHello뒤부터는 많은 내용이 암호화된다는 점- 신분 확인과 마지막 무결성 확인이 따로 존재한다는 점
- Key Exchange — 버전, 암호 모드, 키 재료를 맞춰요.
- Server Parameters — 추가 핸드셰이크 파라미터를 정해요.
- Authentication — 서버를 인증하고, 핸드셰이크가 안 바뀌었는지 확인해요.
메시지 한눈에 요약하면 이래요
여기서는 가지를 잠깐 접어둘게요.
여기서는 인증서 기반 전체 흐름을 중심으로 볼게요. PSK 재개, 0-RTT, HelloRetryRequest 같은 가지는 뒤에서 변형 흐름으로 따로 묶어볼게요.
ClientHello 는 왜 이렇게 많은 걸 한 번에 들고 갈까요?
TLS 1.3의 출발점은 ClientHello 예요. 이 메시지는 “안녕하세요” 하나로 끝나지 않아요. 오히려 앞으로 대화를 어떻게 준비할지에 대한 제안서에 더 가까워요.
RFC 8446 2장은 ClientHello 안에 이런 것들이 들어간다고 설명해요.
- 지원 가능한 TLS 버전 목록
- 지원 가능한 cipher suites
- 보통 Diffie-Hellman key share
- 필요하면 PSK 관련 정보
- 그 밖의 여러 확장 정보
그 제안들은 실제 메시지에서 어디에 들어갈까요?
위 그림은ClientHello가 무슨 제안을 담는지 보여주는 의미 지도였어요. 이제 같은 내용을 실제 전송 순서로 내려가 볼게요. 여기서 supported_versions, key_share, SNI 같은 제안은 각각 독립된 고정 필드가 아니라, 대부분 뒤쪽 extensions 묶음 안의 항목으로 들어가요.
여기서 중요한 건 ClientHello가 딱딱한 고정 20바이트 헤더 같은 구조는 아니라는 점이에요. 대신 핸드셰이크 공통 머리말 + 여러 가변 길이 벡터가 이어지는 구조에 더 가까워요. 그래서 TLS 해부형 글에서는 TCP처럼 32비트 격자를 펼치기보다, 메시지 뼈대와 확장 묶음을 읽는 편이 더 자연스러워요.
첫 번째 표가
ClientHello 바깥 뼈대를 보여줬다면, 아래 표는 그중 extensions 묶음을 한 단계 확대해서 보는 표예요.
즉
ClientHello 는 단순한 인사말이 아니라, 협상 재료를 한꺼번에 깔아두는 첫 장면이에요.
ServerHello 뒤에는 정확히 뭐가 달라질까요?
서버는 ClientHello 를 보고 실제로 쓸 조합을 골라서 ServerHello 로 답해요.
- 어떤 TLS 버전을 쓸지
- 어떤 cipher suite 를 쓸지
- 어떤 key share 를 받아들일지
ClientHello + ServerHello 의 조합이 공유 키 재료를 결정한다고 설명해요. 여기서 중요한 반전이 하나 나와요.
TLS 1.3의 기본 흐름에서는 ServerHello 다음 핸드셰이크 메시지들이 암호화돼요.
이건 TLS 1.2와 비교할 때 사람들이 가장 자주 기억하는 차이 중 하나예요. RFC 8446 1.2절도 주요 차이점으로 “All handshake messages after the ServerHello are now encrypted” 를 직접 적고 있어요.
왜 이게 큰 차이일까요?
예전 감각으로는 인증서나 여러 협상 세부사항이 더 오래 평문에 머무는 쪽이 익숙했어요. 근데 TLS 1.3은 암호화 경계를 더 빨리 세우는 쪽으로 크게 움직였어요. 그래서 중간 관찰자가 볼 수 있는 정보가 줄어들죠.EncryptedExtensions 는 왜 따로 있을까요?
이 이름이 처음엔 좀 낯설어요. “확장을 그냥 ServerHello에 더 넣으면 안 되나요?” 싶죠.
TLS 1.3은 여기서 역할을 분리해요.
ServerHello는 키 교환과 암호화 경계 설정에 꼭 필요한 것에 집중하고,EncryptedExtensions는 그 뒤에 알려도 되는 서버 쪽 추가 파라미터를 담아요.
EncryptedExtensions 를 암호 파라미터 결정에 필수는 아니지만, 핸드셰이크에 필요한 서버 응답 확장을 보내는 자리로 설명해요.
즉 이 메시지는 “이제 문은 닫혔고, 그 안에서 조금 더 자세한 운영 규칙을 알려줄게요” 에 가까워요.
Certificate 와 CertificateVerify 는 왜 둘 다 필요할까요?
이 부분이 초심자가 가장 자주 멈추는 지점이에요.
“인증서 보여줬으면 된 거 아닌가요? 왜 또 Verify가 따로 있어요?”핵심은 이거예요.
Certificate는 “이 공개키와 이 신원 정보를 봐주세요” 에 가까워요.CertificateVerify는 “그리고 그 공개키에 대응하는 개인키를 내가 진짜 갖고 있어요” 를 서명으로 증명해요.
CertificateVerify 를 Certificate 메시지의 공개키에 대응하는 개인키로, 그 시점까지의 핸드셰이크 transcript 에 서명하는 단계라고 설명해요.
즉 Certificate 만 있으면 신분증 사본을 보여준 것에 더 가깝고, CertificateVerify 까지 가야 그 신분증의 진짜 주인임을 현재 세션 위에서 증명한 것에 가까워져요.
여기서 표지판 하나만 더 세워둘게요.
여기서는 인증서 체인 검증 정책 전체를 길게 다루지 않을 거예요. “어떤 이름인지, 누가 발급했는지, 그 공개키를 실제로 가진 주체인지” 가 왜 메시지 둘로 나뉘는지까지만 선명하게 잡으면 충분해요.
Finished 는 무엇을 확인할까요?
Finished 는 이름이 심심해 보여도 되게 중요해요.
RFC 8446 2장은 이 메시지를 그 시점까지의 핸드셰이크 transcript 를 검증하는 MAC 으로 설명해요. 이 메시지가 하는 일은 크게 세 가지예요.
- 키 확인(key confirmation)
- 상대 신원과 지금 세션 키를 묶기
- 지금까지 본 핸드셰이크 transcript 가 안 바뀌었는지 확인하기
Finished 는 “좋아요, 우리 둘 다 지금까지의 대화를 같은 내용으로 보고 있고, 같은 키 쪽으로 도달했어요” 라는 마지막 확인장 같은 거예요.
그래서 Certificate 와 CertificateVerify 만 보고 “이제 끝났네” 라고 읽으면 반쯤만 본 거예요. 마지막 transcript 무결성 확인까지 지나가야 진짜로 핸드셰이크가 닫혀요.
application data의 송신 경계도 여기 붙어 있어요. 0-RTT를 제외하면 각 송신자는 자기 Finished를 보내기 전에는 application data를 보내면 안 돼요. 그래서 서버는 서버 Finished 뒤에 먼저 보낼 수 있고, 클라이언트는 클라이언트 Finished 뒤에 보낼 수 있어요. 흔히 그리는 서버 Finished → 클라이언트 Finished → HTTP 그림은 대표적인 요청 장면을 단순화한 것이고, 프로토콜 규칙 자체는 방향별 경계로 읽는 편이 정확해요.
근데 왜 TLS 1.3은 이렇게 바뀌었을까요?
RFC 8446 1.2절이 짚는 큰 변화들을 사람 말로 풀면 이래요.1. 더 적은 왕복으로 시작하고 싶었어요
클라이언트가ClientHello 에 key share 를 미리 담아 가니까, 서버가 맞는 조합을 골라 바로 ServerHello 로 이어가기 쉬워졌어요. 그래서 대표적인 전체 핸드셰이크가 1-RTT 감각으로 정리돼요.
2. 더 빨리 암호화 경계를 세우고 싶었어요
ServerHello 뒤의 많은 핸드셰이크 메시지가 암호화되면서, 중간 관찰자가 보는 정보가 줄었어요. 이건 성능만이 아니라 개인정보 노출 면적을 줄이는 쪽과도 연결돼요.
3. 낡은 방식은 많이 걷어냈어요
RFC 8446 1.2절은 TLS 1.3이 legacy 알고리즘을 많이 정리했고, static RSA key exchange를 제거했고, forward secrecy 쪽으로 정리했다고 설명해요. 즉 옛날 배경 짐을 많이 덜어낸 버전이라고 보면 돼요.변형 흐름은 어떤 게 있을까요?
기본 전체 흐름만 외워두면 좋은데, 실전에서는 몇 가지 가지치기가 있어요.1. HelloRetryRequest
클라이언트가 처음 보낸 key_share 가 서버가 원하는 그룹과 안 맞으면, 서버는 RFC 8446 2.1절 에 따라 HelloRetryRequest 로 다시 맞는 share를 보내달라고 할 수 있어요.
즉 이건 “실패” 라기보다, 초반 협상 재시도 분기에 가까워요.
2. PSK / 세션 재개
RFC 8446 2.2절은 TLS 1.3이 예전 세션 ID / 세션 티켓 감각을 새 PSK 기반 재개 흐름으로 정리했다고 설명해요. 이미 이전 연결에서 만든 재료를 바탕으로 더 빨리 다시 시작하는 가지예요.3. 0-RTT
TLS 1.3은 0-RTT 데이터도 추가했어요. 왕복을 더 줄일 수 있지만, RFC 8446 1.2절과 8장은 이 모드가 certain security properties 를 희생한다고 설명해요. 특히 많이 알려진 핵심은 이거예요.0-RTT 데이터는 재생(replay)될 수 있어요.여기서 replay는 공격자가 내용을 새로 만들거나 해독한다는 뜻이 아니라, 예전에 유효했던 early data를 다시 보내 서버가 또 처리하게 만들 수 있다는 뜻이에요. 같은 요청을 여러 번 처리해도 최종 상태가 같아지는 멱등(idempotent) 작업보다, 결제·주문 생성·쿠폰 사용처럼 한 번 더 처리될 때 결과가 달라지는 비멱등 요청이 특히 위험해요. 그래서 “빠르다 = 무조건 좋다” 로 보면 안 되고, 애플리케이션이 어떤 요청을 0-RTT에 허용할지, 중복 처리를 어떻게 막을지 조심스럽게 정해야 해요.
잘못 읽기 쉬운 함정 여섯 가지
하나, TLS 1.3은 TLS 1.2에서 메시지 몇 개만 줄인 버전이라고 생각하기.아니에요. 왕복 수, 암호화 경계, 키 파생 구조, 재개 방식까지 꽤 크게 재설계됐어요. 둘, 인증서는 평문으로 항상 다 보인다고 생각하기.
TLS 1.3에서는
ServerHello 뒤의 많은 핸드셰이크 메시지가 암호화돼요.
셋, Certificate 만 오면 서버 인증이 끝났다고 생각하기.CertificateVerify 와 Finished 까지 봐야 현재 세션 위 인증과 무결성 확인이 닫혀요.
넷, 0-RTT는 그냥 더 빠른 정상 모드라고 생각하기.빠르긴 하지만, replay 쪽 성질이 달라서 아무 데이터에나 막 쓰는 감각은 위험해요. 다섯,
HelloRetryRequest 를 예외적인 오류 메시지 정도로만 보기.실제로는 key share가 안 맞을 때 나오는 정식 협상 분기예요.
자, 정리해볼까요?
- TLS 1.3 핸드셰이크는
ClientHello→ServerHello→EncryptedExtensions→Certificate→CertificateVerify→Finished흐름으로 읽으면 큰 뼈대가 잡혀요. - 이 글의 핵심은 각 메시지가 프로토콜 안에서 왜 필요한지를 잡는 거예요.
ClientHello와ServerHello는 협상과 키 재료의 출발점이고,ServerHello뒤에는 후반부 메시지가 더 보호된 상태로 이어져요.Certificate와CertificateVerify는 신원 제시와 개인키 보유 증명을 나눠 맡고,Finished는 마지막 무결성 확인을 닫아요.- 변형 흐름으로는
HelloRetryRequest, PSK 재개, 0-RTT 같은 가지가 붙을 수 있어요.
이어서 보면 좋은 글
- TLS가 왜 필요한지, 인증서와 보호 통로의 큰 그림부터 다시 잡고 싶다면 — TLS, SSL, 인증서는 뭐가 다를까요?
- 같은 메시지들을 이번에는 실제 장면처럼 한 단계씩 따라가고 싶다면 — TLS 핸드셰이크는 실제로 어떻게 한 단계씩 진행될까요?
Certificate뒤에서 실제로 이름 확인, 체인 검증, 신뢰 실패가 어떻게 갈라지는지 장면으로 보고 싶다면 — TLS 인증서 체인과 신뢰 오류는 어떻게 읽어야 할까요?- 요청 하나를 따라가다가 TLS 단계에서 멈춘다는 감각을 다시 붙여보고 싶다면 — End-to-End Request Debugging
- 평문 TCP 연결 위에 TLS가 올라가기 전, 아래 연결 자체는 어떻게 열리는지 다시 보고 싶다면 — TCP 3-way handshake는 왜 세 번이나 주고받을까요?
- 상태 목록 다음 단계로, 실제 패킷 줄과 캡처 장면을 읽는 감각을 붙이고 싶다면 — tcpdump 한 줄은 어떻게 읽어야 할까요?