TLS/HTTPS는 어떻게 도청 불가능한 채널을 만드나 — 핸드셰이크부터 인증서 체인까지
TLS/HTTPS 핸드셰이크(TLS 1.3 1-RTT·0-RTT), 비대칭으로 대칭키를 합의하는 원리, 인증서 체인과 CA 신뢰, forward secrecy, SNI·ALPN, mTLS까지 암호화 채널의 작동을 설명합니다.
“자물쇠 아이콘 하나에 담긴 세 가지 약속”
브라우저 주소창의 자물쇠는 세 가지를 보장합니다 — 기밀성(중간에서 못 본다), 무결성(중간에서 못 바꾼다), 인증(상대가 진짜 그 서버다). TLS(Transport Layer Security)는 이 세 가지를 평문 TCP 위에 얹는 얇은 층입니다. HTTPS = HTTP + TLS일 뿐입니다.
문제는 이걸 “암호화하니까 안전” 한 줄로 알면, 인증서 만료로 전 서비스가 멈추거나, 체인 누락으로 일부 클라이언트만 실패하거나, SNI/ALPN을 몰라 HTTP/2가 안 잡히는 사고를 못 푼다는 점입니다. 이 글은 핸드셰이크가 메시지 단위로 무슨 키를 어떻게 합의하는지, 그리고 인증서 체인이 왜 신뢰를 만드는지까지 내려갑니다.
TLS의 위치: TCP 위, HTTP 아래
TLS는 계층 관점에서 TCP(신뢰적 바이트스트림) 위에서 동작합니다. TCP가 “순서대로 안 잃고” 날라주면, TLS는 그 위에서 “암호화·인증”을 더하고, 그 위에서 HTTP가 평문처럼 동작합니다. 그래서 애플리케이션은 TLS를 거의 의식하지 않습니다.
flowchart TD
A[HTTP 요청/응답] --> B["TLS 레코드 계층<br/>암호화 + MAC(무결성)"]
B --> C["TCP<br/>순서·신뢰 보장"]
C --> D[IP / 네트워크]
핵심 트릭: 비대칭으로 합의하고, 대칭으로 일한다
대칭키 암호(AES)는 빠르지만, 양쪽이 같은 키를 미리 공유해야 합니다 — 처음 만난 서버와 어떻게? 비대칭키(공개키/개인키)는 이 문제를 풀지만 느립니다. TLS의 전략: 비대칭키로 “대칭키를 안전하게 합의” 하고, 이후 실제 데이터는 빠른 대칭키로 암호화합니다. 두 세계의 장점만 취합니다.
현대(TLS 1.3)는 합의 방식으로 (EC)DHE — 키 교환마다 임시 키쌍을 쓰는 방식을 표준으로 합니다. 덕분에 forward secrecy(나중에 서버 개인키가 유출돼도, 과거에 녹음해 둔 트래픽은 복호화 불가)가 보장됩니다.
핸드셰이크를 움직임으로 보기 (TLS 1.3, 1-RTT)
ClientHello로 시작해, 단 한 번의 왕복(1-RTT) 만에 양쪽이 같은 세션 키를 갖고 암호화된 통신을 시작합니다. 파랑은 클라이언트→서버, 초록은 서버→클라이언트입니다.
TLS 1.2는 같은 일을 2-RTT로 했습니다(ServerHello 후 키 교환에 한 번 더 왕복). 1.3은 ClientHello에 미리 키 공유 후보를 실어 1-RTT로 줄였고, 재방문 시에는 이전 세션 정보를 활용해 0-RTT(첫 패킷에 데이터까지)도 가능합니다 — QUIC/HTTP3의 빠른 연결이 이 위에 있습니다.
인증: “이 공개키가 진짜 그 서버 것”을 누가 보증하나
암호화만으로는 중간자(MITM) 를 못 막습니다 — 공격자와 안전하게 암호화하는 건 의미가 없으니까요. 그래서 서버의 공개키가 진짜 그 도메인 소유자 것임을 제3자(CA, 인증 기관)가 보증합니다. 이게 인증서입니다.
신뢰는 체인으로 이어집니다. 브라우저/OS에는 소수의 루트 CA가 미리 박혀 있고(트러스트 스토어), 루트가 중간 CA를 서명하고, 중간 CA가 서버 인증서를 서명합니다. 클라이언트는 서버 인증서 → 중간 → 루트까지 서명을 거슬러 검증해, 루트에 닿으면 신뢰합니다.
현장 1위 사고: “중간 인증서 누락”. 서버가 자기 인증서만 내려보내고 중간 CA를 빠뜨리면, 중간 CA를 이미 캐시한 브라우저는 통과하지만 그렇지 않은 클라이언트(일부 모바일·서버 간 호출)는 체인을 못 이어 실패합니다. “어떤 기기에서만 SSL 오류”의 전형입니다. 서버는 풀 체인(서버+중간) 을 함께 제공해야 합니다.
핸드셰이크에 끼어드는 두 협상: SNI와 ALPN
한 IP/포트에 여러 도메인을 호스팅할 때(가상 호스팅·CDN), 서버는 “어느 도메인의 인증서를 줄지” 알아야 합니다. 그런데 그건 암호화 전에 정해야 하죠. 그래서 ClientHello가 SNI(Server Name Indication) 에 목적지 도메인을 (1.2에선 평문으로) 담습니다 — SNI가 틀리면 엉뚱한 인증서가 와서 실패합니다. 또 ClientHello의 ALPN으로 h2/http/1.1을 협상해 HTTP 버전을 정합니다.
mTLS: 서버뿐 아니라 클라이언트도 증명한다
기본 TLS는 서버만 인증합니다(클라이언트는 익명). mTLS(상호 TLS) 는 클라이언트도 인증서를 제시해 서로를 검증합니다. 사람이 아니라 서비스 간 통신에서 “이 호출자가 진짜 우리 서비스인지”를 보증하는 핵심 수단이라, 서비스 메시·zero trust의 토대가 됩니다.
디버깅
1
2
3
4
5
6
7
8
# 핸드셰이크·인증서 체인·협상된 버전/암호군을 직접 본다
openssl s_client -connect example.com:443 -servername example.com </dev/null
# 인증서 만료일·체인만 빠르게
echo | openssl s_client -connect example.com:443 2>/dev/null | openssl x509 -noout -dates -issuer -subject
# ALPN으로 h2가 협상되는지
openssl s_client -alpn h2 -connect example.com:443 </dev/null | grep -i alpn
s_client는 “체인이 끝까지 이어지나(Verify return code: 0 (ok))”, “어떤 TLS 버전·암호군이 잡혔나”, “SNI를 줬을 때 올바른 인증서가 오나”를 한눈에 보여줘 만료·체인·SNI 세 사고를 가릅니다.
면접/리뷰 단골 질문
- Q. 비대칭키가 있는데 왜 대칭키로 바꾸나? → 비대칭은 느려서 데이터 암호화엔 부적합. 비대칭으로 대칭키를 합의만 하고, 데이터는 빠른 대칭키(AES)로 처리.
- Q. forward secrecy가 뭐고 왜 중요? → 매 세션 임시 키((EC)DHE)를 써서, 서버 개인키가 나중에 유출돼도 과거 트래픽은 복호화 불가. TLS 1.3은 이를 사실상 강제.
- Q. 일부 클라이언트에서만 SSL 오류가 난다. 첫 의심은? → 중간 인증서 누락(풀 체인 미제공).
openssl s_client로Verify return code확인. - Q. TLS 1.3이 1.2보다 빠른 이유는? → 핸드셰이크를 2-RTT→1-RTT로 줄이고, 재방문 시 0-RTT 지원 + 취약한 레거시 암호군 제거.
정리
- TLS = TCP 위, HTTP 아래에서 기밀성·무결성·인증을 더하는 층. HTTPS = HTTP+TLS.
- 비대칭으로 대칭키 합의 → 대칭키로 데이터 처리. 1.3은 (EC)DHE로 forward secrecy를 기본화.
- 인증은 인증서 체인(서버→중간→루트 CA)으로 신뢰를 형성 — 중간 인증서 누락이 최다 사고.
- 핸드셰이크에 SNI(어느 도메인) + ALPN(어느 HTTP 버전)이 함께 협상되고, mTLS는 서비스 간 신뢰의 토대.
다음 글: 이제 패킷이 사설망과 공인망을 넘나드는 NAT와 사설망으로 이어집니다.
