iBetter Books
수정

HTTPS와 TLS 핸드셰이크

http://로 시작하는 주소와 https://로 시작하는 주소는 겉으로 비슷해 보이지만 근본적으로 다릅니다. HTTP는 평문으로 통신합니다. 중간에서 패킷을 가로채면 내용을 그대로 읽을 수 있습니다. HTTPS는 HTTP에 TLS(Transport Layer Security)를 입혀 암호화합니다. 같은 패킷을 가로채도 의미 있는 내용을 꺼낼 수 없습니다.

TLS가 하는 일

TLS는 세 가지를 보장합니다.

첫째, 기밀성(confidentiality)입니다. 데이터를 암호화해 제3자가 읽지 못합니다.

둘째, 무결성(integrity)입니다. 데이터가 전송 중에 변조되지 않았음을 검증합니다.

셋째, 인증(authentication)입니다. 내가 접속하는 서버가 진짜 그 서버인지 확인합니다. 인증서를 통해 이루어집니다.

TLS 핸드셰이크 개요

TCP 연결이 성립된 후, 실제 HTTP 요청을 보내기 전에 TLS 핸드셰이크가 일어납니다. TLS 1.3 기준으로 과정을 간략히 설명합니다.

클라이언트가 먼저 인사를 건넵니다. 지원하는 암호화 알고리즘 목록과 난수를 보냅니다. 서버는 사용할 알고리즘을 선택하고, 인증서와 함께 응답합니다. 클라이언트는 인증서를 검증합니다. 인증서가 신뢰할 수 있는 인증 기관(CA, Certificate Authority)에서 서명된 것인지, 만료되지 않았는지, 도메인이 맞는지 확인합니다. 검증을 통과하면 세션 키를 협상합니다. 이후 모든 통신은 이 세션 키로 대칭 암호화됩니다.

TLS 1.3은 TLS 1.2보다 핸드셰이크를 한 번 줄였습니다. 클라이언트가 첫 메시지에서 이미 키 교환 데이터를 함께 보내므로, 전체 핸드셰이크가 1-RTT(왕복 1회)로 완료됩니다.

인증서의 역할

인증서가 없으면 클라이언트는 접속한 서버가 진짜인지 알 수 없습니다. 공격자가 가짜 서버를 세우고 진짜인 척해도 막을 방법이 없습니다. 이것을 중간자 공격(Man-in-the-Middle attack)이라 합니다.

인증서는 신뢰할 수 있는 제3자, 즉 CA(인증 기관)가 서명합니다. 브라우저와 운영체제는 신뢰하는 CA 목록을 내장하고 있습니다. 서버의 인증서가 이 목록에 있는 CA로 서명되었다면, 클라이언트는 그 서버를 신뢰합니다.

오늘날 Let's Encrypt 덕분에 HTTPS 인증서를 무료로 발급받을 수 있습니다. 과거에는 인증서 비용이 HTTPS 도입의 장벽이었지만, 이제는 그 이유가 없어졌습니다.

curl로 HTTPS 핸드셰이크 보기

curl -v 옵션을 사용하면 TLS 협상 과정을 터미널에서 직접 볼 수 있습니다.

# 파일: 터미널curl -v https://example.com

출력에서 *로 시작하는 줄이 curl의 연결 정보입니다.

*   Trying 93.184.216.34:443...
* Connected to example.com (93.184.216.34) port 443
* SSL connection using TLSv1.3 / TLS_AES_256_GCM_SHA384
* Server certificate:
*  subject: CN=www.example.org
*  start date: Jan 15 00:00:00 2025 GMT
*  expire date: Feb 14 23:59:59 2026 GMT
*  SSL certificate verify ok.
> GET / HTTP/2
> Host: example.com
...
< HTTP/2 200
< content-type: text/html; charset=UTF-8

눈여겨볼 지점이 세 곳입니다. SSL connection using TLSv1.3에서 TLS 버전과 사용된 암호화 스위트를 확인할 수 있습니다. Server certificate 블록에서 인증서의 유효 기간과 검증 결과를 볼 수 있습니다. GET / HTTP/2에서 HTTP/2로 요청이 이루어지는 것을 확인할 수 있습니다. 포트 443은 HTTPS의 기본 포트입니다. HTTP는 80번입니다.

다음 장에서는 이 HTTP와 TCP를 직접 다루는 소켓 프로그래밍으로 내려갑니다.