안동민 개발노트 아이콘

안동민 개발노트

10장 : HTTPS와 네트워크 보안

TLS 핸드셰이크

HTTPS 연결은 TCP 연결 위에서 바로 HTTP를 보내지 않습니다. 먼저 TLS 핸드셰이크로 “어떤 버전과 암호 조합을 쓸지”, “서버가 그 서비스 이름에 맞는 인증서를 제시했는지”, “이번 연결에만 쓸 트래픽 키를 어떻게 만들지”를 합의합니다.


TLS 핸드셰이크가 정하는 것

핸드셰이크의 결과는 크게 세 가지입니다. 첫째, 클라이언트와 서버가 TLS 버전과 암호 조합을 고릅니다. 둘째, 인증서와 서명으로 서버 신원을 확인합니다. 셋째, ECDHE 같은 임시 키 교환으로 공유 비밀을 만들고, 거기서 이번 연결의 여러 트래픽 키를 파생합니다.


TLS 1.2 전체 핸드셰이크

일반적인 TLS 1.2 full handshake는 TLS만 보면 2 RTT가 필요합니다. TCP 3-way handshake까지 합치면, HTTP 요청을 안전하게 보내기까지 보통 3 RTT가 걸립니다. 단, 세션 재개, False Start, TCP Fast Open 같은 최적화가 있으면 실제 지연은 달라질 수 있습니다.

구성 요소TLS 1.2에서의 위치TLS 1.3에서의 변화
버전 협상ClientHello, ServerHelloClientHello의 supported_versions 확장으로 협상
키 교환RSA key transport, DHE, ECDHE 등이 가능일반 full handshake는 (EC)DHE key_share, 재개는 PSK 또는 PSK+(EC)DHE
인증인증서와 ServerKeyExchange 서명 등CertificateVerify로 핸드셰이크 transcript에 서명
데이터 보호CBC, GCM 등 여러 방식AEAD 기반 cipher suite만 사용

Cipher Suite 읽기

TLS 1.2의 cipher suite 이름은 키 교환, 인증, 대칭 암호, 해시가 한 줄에 들어 있습니다. 예를 들어 TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256은 ECDHE로 공유 비밀을 만들고, RSA 인증서 키로 서버 서명을 검증하며, AES-128-GCM으로 레코드를 보호하고, SHA-256을 키 유도와 Finished 검증에 사용한다는 뜻입니다.

TLS 1.3의 cipher suite 이름은 더 짧습니다. TLS_AES_128_GCM_SHA256처럼 데이터 보호 알고리즘과 해시만 담고, 키 교환과 인증 방식은 별도 확장과 인증서 알고리즘으로 협상합니다.


디피-헬만 키 교환

ECDHE(Elliptic Curve Diffie-Hellman Ephemeral)는 현재 TLS에서 가장 중요한 키 교환 방식입니다. 공개 채널에는 기준점과 공개값만 오가고, 각자의 임시 비밀값은 네트워크에 나오지 않습니다. 양쪽은 상대의 공개값과 자신의 비밀값을 조합해 같은 공유 비밀에 도달합니다. 다만 순수한 디피-헬만만으로는 상대가 누구인지 인증하지 못하므로, TLS는 인증서와 서명으로 공개값이 진짜 서버 흐름에 속했는지 확인합니다.

여기서 E(Ephemeral)는 매 연결마다 새 임시 키 쌍을 만든다는 뜻입니다. 서버의 장기 개인키가 나중에 유출되어도, 과거 연결의 임시 비밀값이 안전하게 폐기되었고 세션 키 로그나 티켓 키 유출 같은 별도 사고가 없다면 녹화된 과거 트래픽을 다시 풀기 어렵습니다. 이 성질을 전방 비밀성(Forward Secrecy)이라고 합니다.


TLS 1.3의 개선점

TLS 1.3은 2018년에 표준화되었고, TLS 1.2보다 핸드셰이크를 짧고 단단하게 만들었습니다. 클라이언트가 ClientHello에 (EC)DHE key_share를 미리 싣기 때문에, 일반적인 full handshake에서는 서버 응답을 한 번 받은 뒤 바로 암호화 상태로 들어갈 수 있습니다.

변경 사항TLS 1.2TLS 1.3
Full handshake보통 2 RTT보통 1 RTT
0-RTT없음재개 연결에서 일부 early data 가능
정적 RSA/DH가능하지만 권장되지 않음제거
핸드셰이크 암호화많은 메시지가 평문ServerHello 이후 메시지 암호화
Cipher suite키 교환·인증까지 이름에 포함AEAD와 해시 중심
재협상존재제거

0-RTT는 “재접속을 더 빨리 시작할 수 있는 기능”이지, 기본으로 켜 둘 만능 최적화가 아닙니다. 0-RTT early data는 replay 위험이 있고 1-RTT 데이터보다 보안 성질이 약하므로, 멱등 요청처럼 재전송되어도 안전한 작업에만 제한적으로 써야 합니다.

tls_check.sh
# SNI를 명시해 TLS 1.3 연결이 되는지 확인
openssl s_client -connect example.com:443 -servername example.com -tls1_3 </dev/null 2>/dev/null | grep "Protocol"

# 협상된 cipher suite 확인
openssl s_client -connect example.com:443 -servername example.com </dev/null 2>/dev/null | grep "Cipher"

# leaf 인증서의 주체와 유효기간 확인
openssl s_client -connect example.com:443 -servername example.com </dev/null 2>/dev/null \
  | openssl x509 -noout -subject -issuer -dates

# 서버가 보내는 인증서 체인 확인
openssl s_client -connect example.com:443 -servername example.com -showcerts </dev/null 2>/dev/null \
  | grep -E "s:|i:"

인증서와 서버 신원

TLS 핸드셰이크에서 서버가 보내는 X.509 인증서는 “이 공개키는 이 서비스 이름에 묶여 있고, 신뢰할 수 있는 CA가 그 사실을 서명했다”는 주장입니다.

브라우저는 단순히 “인증서가 있다”만 보지 않습니다. 접속한 DNS 이름이 인증서의 subjectAltName(dNSName)에 있는지, 유효기간 안인지, 용도와 확장이 맞는지, 그리고 인증서 체인이 신뢰 저장소의 trust anchor까지 이어지는지를 함께 확인합니다. 현대 검증에서는 도메인 이름을 Common Name(CN)에 기대기보다 SAN을 기준으로 봅니다.


인증서 체인 검증

서버는 보통 leaf 인증서와 intermediate CA 인증서를 보냅니다. 루트 CA 인증서는 대개 서버가 보내는 대상이 아니라, 운영체제나 브라우저의 신뢰 저장소에 들어 있는 trust anchor입니다.

루트 CA가 모든 서버 인증서를 직접 서명하지 않는 이유는 운영과 사고 격리 때문입니다. 루트 개인키는 오프라인에 가깝게 보호하고, 실제 발급 업무는 중간 CA가 맡습니다. 중간 CA에 문제가 생기면 해당 중간 CA를 폐기하거나 교체할 수 있지만, 루트 CA가 유출되면 그 루트에 기대는 신뢰 체계 전체가 흔들립니다.

다음 절에서는 실무에서 인증서를 발급하고 갱신하는 방법을 살펴보겠습니다.