본문으로 건너뛰기
Previous
Next
TLS 프로토콜 심화 가이드 | 핸드셰이크·암호 스위트·TLS 1.3 키 유도

TLS 프로토콜 심화 가이드 | 핸드셰이크·암호 스위트·TLS 1.3 키 유도

TLS 프로토콜 심화 가이드 | 핸드셰이크·암호 스위트·TLS 1.3 키 유도

이 글의 핵심

TLS 1.2와 1.3의 핸드셰이크 차이, cipher suite(키 교환·인증·대칭키·해시) 해체, AEAD·완전 순방향 비밀성·0-RTT 트레이드오프, QUIC·HTTP/3와의 결합까지 전문가 수준으로 정리합니다.

TLS(Transport Layer Security)는 HTTP든 메일이든, TCP 위에서 돌아가는 앱에 기밀성·무결성·서버(그리고 필요하면 클라이언트) 인증을 붙이는 쪽에 가깝죠. “배포만 하면 끝”이 아니라, 인증서·체인·만료랑 핸드셰이크·cipher·키가 한 덩어리로 지연이랑 보안 느낌을 같이 만든다는 걸 알아두면, 장애 났을 때 훨씬 덜 당황해요. RFC 8446(1.3)·5246(1.2) 쪽 느낌으로, 프로토콜 안을 대충 훑는 글이에요.

인증서 만료로 한번 크게 박살 난 적이 있어요(제 잘못이었고요, 알람을 안 붙여 둔 거). 갑자기 브라우저는 빨갛고, curl은 인증서 에러, 모니터링은 “200인데 뭔가 이상” 같은 소리만… 결국 openssl s_client -connect host:443 -servername host로 체인이랑 만료일을 보고, CA 대시보드에서 갱신하고 nginx 리로드하는 삼콤보만 해도 끝났는데, 그때 30분을 헤맨 건 “TLS가 뭔지”가 아니라 “우리가 뭘 잊었는지”를 몰라서였어요. 그 이후론 만료 30일 전 알림이랑 스테이징에서 먼저 갱신이 기본 루틴이 됐죠. 사설 CA나 엔터프라이즈 EV까지 안 가도, 공개 웹이면 Let’s Encrypt면 충분해요 — 돈 쓰는 쪽이 주는 “안심”보다, 자동 갱신·감시가 덜 꼬이는 게 훨씬 이득인 경우가 많아요(물론 내부망 mTLS는 이야기가 다르고요).

읽다 보면 “TLS 1.2랑 1.3, 핸드셰이크가 왜 달라?”·“cipher suite 문자열 뭐냐?”·“PFS, AEAD, 0-RTT가 뭔 느낌이냐?” 정도는 설명할 수 있게 될 거고, QUIC/HTTP/3이랑도 대충 이어질 거예요. TCP/UDP 쪽은 TCP 가이드UDP 가이드를 같이 보면 스택이 덜 뜬구름 같아요.


TLS가 챙기는 건 대충 이 셋이에요. 기밀성 — 도청자한테 평문이 안 감. 무결성 — 가운데 누가 바꿨는지(MAC이나 AEAD로) 잡는 것. 인증 — 보통은 서버가 X.509로 “나 맞다”를 증명. TLS는 TCP를 대체하는 게 아니라, “신뢰할 만한 키 재료”를 협상한 다음 대칭키로 벌크를 싸는 쪽이에요.

TLS 1.2 풀 핸드셰이크는 대략 ClientHello에서 cipher 목록·키 교환 후보만 쏘고, ServerHello로 하나 고르고, Certificate로 체인·공개키를 보내고, ClientKeyExchange 쪽에서 premaster 쪽이 이어지고, ChangeCipherSpec / Finished로 마무리… 같은 흐름이에요(모드에 따라 덜고 붙이기도 하고). 포인트는 ClientHello에 올라온 cipher suite 후보 중 서버가 하나를 집는다는 거고, Certificate로 서버 정체 + 체인이 오면 클라는 신뢰 앵커SNI/ SAN 호스트이름을 같이 봐요. premaster → master → PRF로 키 블록 뽑는 TLS 1.2의 PRF·키 뽑기는 세부가 cipher에 묶여 있고요, 세션 재개로 풀 핸드셰이크 횟수를 줄이는 것도 “지연 vs 서버 상태”랑 직결돼요.

sequenceDiagram
  participant C as 클라이언트
  participant S as 서버
  C->>S: ClientHello (랜덤, 세션, cipher suites 등)
  S->>C: ServerHello
  S->>C: Certificate
  S->>C: ServerKeyExchange (일부 키 교환)
  S->>C: CertificateRequest (선택, mTLS)
  S->>C: ServerHelloDone
  C->>C: 인증서 검증 (CA·호스트명)
  C->>S: ClientKeyExchange
  C->>S: CertificateVerify (mTLS 시)
  C->>S: ChangeCipherSpec
  C->>S: Finished (협상된 키로 HMAC)
  S->>C: ChangeCipherSpec
  S->>C: Finished
  Note over C,S: 애플리케이션 데이터 (레코드 레이어)

TLS 1.3은 “보안이랑 단순화” 쪽으로 많이 휩쌌어요. RSA로 premaster를 싸보내는 예전 키 전송은 빠졌고(그 패턴은 PFS를 약하게 만든 쪽이 있었죠), (EC)DHE 계열이 중심이에요. 공유 비밀에서 HKDF로 핸드셰이크 트래픽·애플리케이션 트래픽 키를 뽑는 식. 메시지가 암호화되는 구간이 늘어서 중간자가 협상을 덜 훔쳐보는 구조 쪽이고, 0-RTT(early data) 는 첫 방향에 데이터를 먼저 싣는 대신 리플레이를 같이 끌고 와서, 결제 POST 같은 데 쓰면 위험하죠 — 멱등하고 덜 민감한 것만, 그리고 HTTP 쪽 Early Data 가이드(RFC 8470 계열)랑 앱 정책이 같이 있어야 합니다. 제 취향으론 0-RTT는 기본 켜지 말고, 정말 이득이 클 때만 제한하는 편이 마음이 편해요.

TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 같은 1.2 cipher 이름은 ECDHE가 일시 키 교환이라 PFS 쪽에 기여하고, RSA는 인증서 공개키 알고리즘(서명 검증) 쪽, AES_256_GCM이 대칭+모드(여기선 AEAD), SHA384는 PRF·핸드셰이크 쪽에 묶이는 느낌이에요. DHE/ECDHE가 있으면 세션 키가 연결마다 갈리고, 과거 트래픽 복호화가 어려워지는 쪽(완전 순방향 비밀성)에 기여하죠. 1.3 쪽 suite 이름은 대칭·해시 위주로 줄었고, 키 교환은 KeyShare 쪽에서 따로 봐요. 구현체만 보면 1.2 문자열이랑 1.3 이름이 동시에 잡혀 있는 경우가 많고요. CBC+HMAC 쪽은 역사적으로 패딩·오라클이랑 엮였고, AEAD가 암호화+무결성을 한 덩어리로 처리해서 레코드 레이어를 단순하게 만드는 쪽—GCM, ChaCha20-Poly1305 같은 느낌.

키 유도만 짚으면, 1.2는 premaster → master → PRF로 쓰기/MAC/IV 쪽 키 블록을 잘라 쓰고, 1.3은 shared secret + 핸드셰이크 트랜스크립트에 HKDF를 단계적으로 얹고, Key Update로 같은 연결 안에서도 키를 돌릴 수 있어요. 레코드는 상위 바이트를 끊어서 시퀀스·타입·버전이랑 같이 인증·암호화—캡처 볼 땐 Wireshark의 TLS 디코딩 믿는 게 안전하다는 정도(1.3은 레거시 호환 디테일도 있고요).

QUIC은 UDP인데, 암호·핸드셰이크를 프로토콜에 박아 넣는 쪽이라 “TCP 위에 TLS를 또 올린 것”이랑 결합 방식이 달라요. TLS 1.3 핸드셰이크 기록이 QUIC 패킷 보호 쪽이랑 어떻게 맵되는지, UDP 가이드의 QUIC 쪽이 더 시원해요.

운영 측에선 만료·체인 불완전·이름 mismatch는 여전히 1순위 장애이고, 실제로 뭐가 잡혔는지 openssl s_client나 서버 로그로 버전·cipher·ALPN(h2/h3) 를 확인하라는 이야기는 여전해요. mTLS 쓰는 내부망은 인증서 회전이 수동이면 갑자기 지옥이 열려서, “자동이 안 되면 아예 쓰지 말지” 쪽이 현실적이에요. C++에서 OpenSSL·Asio로 손대보려면 C++ SSL/TLS 글이 이어지고요.

HTTP 의미 쪽이 궁하면 HTTP 완전 가이드를 보면 돼요. 끝으로 한 줄: 인증서가 제일 먼저 터지고, 그다음이 협상·성능, 그다음이 0-RTT 같은 “편의의 대가”예요. 전부 한 번씩 잘못 밟아봐야 몸이 기억하니까, 만료 날짜만은 지금 캘린더에 박아 두세요(진짜로요).