HTTPS는 HTTP를 어떻게 감싸는가: TLS와 인증서 이야기

HTTPS는 HTTP를 어떻게 감싸는가: TLS와 인증서 이야기

브라우저 주소창에 작은 자물쇠 아이콘이 켜져 있으면 우리는 별다른 의심 없이 정보를 입력합니다. 비밀번호를 치고, 카드 번호를 넣고, 메시지를 보내는데요. 이 자물쇠 한 칸이 사실은 HTTP 위에 한 겹의 보호막을 덧씌운 결과인 셈입니다.

HTTPS는 이름에서도 짐작되듯 HTTP에 보안(Secure)을 보탠 형태입니다. 정확히 말하면 HTTP를 TLS라는 암호 통신 위에서 흘려보내는 구조인데요. 이번 글에서는 평문 HTTP가 왜 위험한지, TLS가 그 위에 무엇을 더하는지, 그리고 우리가 평소에 보는 인증서와 자물쇠가 어떻게 신뢰를 만드는지를 차근차근 풀어보겠습니다.

평문이 위험한 이유

HTTP만으로 통신하던 시대에는, 같은 네트워크에 앉은 누구나 패킷을 들여다볼 수 있었습니다. 패킷에 담긴 글자는 사람이 읽을 수 있는 평문이라, 와이파이 같은 공유 매체에서는 옆자리 사람이 내가 보낸 비밀번호를 그대로 볼 수 있었던 셈인데요. 조금 더 적극적인 공격자라면 패킷 일부를 바꿔치기해서, 보낸 사람과 받은 사람이 영영 알아채지 못하는 상태로 메시지를 조작할 수도 있었습니다.

심지어 통신 상대가 진짜 그 서버인지조차 보장할 수 없었습니다. DNS 응답을 가로채거나 ARP 테이블을 흔들면, 같은 도메인 이름으로 들어와도 실제로는 공격자의 서버에 연결되도록 유도할 수 있었거든요. 이 세 가지 문제—엿보기, 바꿔치기, 가짜 서버를 한 번에 다루기 위해 등장한 것이 TLS이고, 그 위에 HTTP를 얹은 결과가 HTTPS입니다.

HTTP를 감싸는 TLS 한 겹

TLS는 Transport Layer Security의 줄임말입니다. 이름 그대로 TCP 같은 전송 계층 위에서 동작하면서, 그 위로 흐르는 데이터를 암호화해 주는 별도의 약속인데요. HTTPS는 새로운 프로토콜을 발명한 것이 아니라, “HTTP 메시지를 TLS로 한 번 감싸서 보내자”는 합의에 가깝습니다.

계층이 쌓이는 모습
응용 계층:   HTTP
보안 계층:   TLS  ← 여기가 추가됐다
전송 계층:   TCP (또는 HTTP/3에서는 UDP + QUIC)
네트워크:    IP

이렇게 한 겹이 더 들어가면 응용 코드에서는 거의 변화가 없습니다. 같은 GET 요청을 같은 헤더로 보내면 되고, TLS는 그 데이터를 받아 암호화한 뒤 TCP로 흘려보내는 일을 알아서 처리하죠. 서버 쪽에서도 TLS 라이브러리가 받아서 복호화한 다음 HTTP 메시지로 풀어주기 때문에, 양쪽 응용 코드는 평문 시절과 거의 똑같은 인터페이스를 그대로 씁니다.

TLS 핸드셰이크가 하는 일

암호화된 통신을 시작하려면, 먼저 둘이 같은 비밀 키를 갖고 있어야 합니다. 그런데 이 키를 어떻게 안전하게 나눠 가질 수 있을까요? 공격자가 통신을 모두 보고 있는 상황에서 키를 그대로 보낼 수는 없습니다. 이 문제를 푸는 절차가 바로 TLS 핸드셰이크입니다.

단순화한 TLS 핸드셰이크
   클라이언트                          서버
       │                                │
       │── ClientHello ────────────────▶│   "이 버전과 알고리즘 쓸까?"
       │                                │
       │◀── ServerHello + 인증서 ──────│   "그래, 내 인증서 받아"
       │                                │
       │── 키 합의용 메시지 ──────────▶│
       │◀── 키 합의용 메시지 ──────────│
       │                                │
       │═════ 암호화된 통신 시작 ══════│

핵심은 두 가지입니다. 하나는 서로 합의해 만든 비밀 키로 이후 통신을 암호화한다는 점이고, 다른 하나는 이 비밀 키를 만들 때 누가 보고 있어도 가로챌 수 없도록 설계되었다는 점인데요. 요즘은 ECDHE 같은 키 교환 방식이 기본으로 쓰이는데, 이 방식은 매번 새 임시 키를 만들어 쓰기 때문에 서버의 장기 키가 나중에 새더라도 과거 통신 내용까지 풀리지는 않게 만듭니다(이를 forward secrecy라고 부릅니다).

인증서가 만드는 신뢰의 사슬

핸드셰이크에서 또 한 가지 중요한 일이 일어나는데, 바로 서버가 자신이 누구인지를 증명하는 단계입니다. 서버는 ServerHello에 자신의 디지털 인증서를 함께 보내고, 클라이언트는 이 인증서를 검증해 “이 서버가 정말 example.com의 주인인가”를 확인하죠.

인증서는 공개 키와 도메인 이름, 유효 기간 같은 정보를 담은 문서입니다. 그리고 그 문서 끝에는 인증 기관(CA, Certificate Authority)의 서명이 붙어 있는데요. 브라우저나 운영 체제는 미리 신뢰할 만한 CA들의 공개 키를 자기 안에 들고 있어서, 그 서명을 검증하면 인증서를 믿을 수 있는지 판단할 수 있습니다.

여기서 흥미로운 부분은 인증서가 한 단계로 검증되지 않는다는 점입니다. 실제로는 보통 “내 사이트 인증서 → 중간 인증서 → 루트 인증서” 식으로 사슬이 이어지는데요. 브라우저는 사슬의 끝(루트)을 자기가 알고 있는지를 확인해서, 만약 안다면 그 사슬 전체를 신뢰합니다. 이 구조를 신뢰의 사슬(chain of trust)이라고 부르고, HTTPS의 신원 보장이 작동하는 토대입니다.

만약 인증서의 도메인이 실제 접속한 도메인과 다르거나, 사슬이 깨졌거나, 만료된 인증서라면 브라우저는 빨간 경고 화면을 띄웁니다. “이 사이트는 안전하지 않습니다” 같은 경고가 그 결과인데요. 공격자가 가짜 서버를 세워도 정상 인증서를 발급받을 수 없기 때문에, 이 단계에서 가로채기 시도가 발각됩니다.

인증서를 발급받을 때 한 가지 더 알아두면 좋은 것이 있습니다. 한 인증서가 보호할 수 있는 도메인 범위는 인증서 안의 SAN(Subject Alternative Name) 필드로 정해지는데, 여기에 여러 도메인을 함께 적어두면 한 장으로 여러 사이트를 보호할 수 있습니다. 와일드카드 인증서(*.example.com)는 한 단계 하위 도메인을 모두 묶어 보호해 주는 형태라 운영이 편하지만, 키 하나가 새면 모든 하위 도메인이 함께 영향을 받는다는 점은 기억해둘 필요가 있습니다.

HTTPS가 지켜주는 것, 그리고 못 지키는 것

HTTPS가 기본적으로 보장하는 것은 세 가지입니다. 첫째는 통신 내용이 중간에서 읽히지 않게 하는 기밀성(confidentiality), 둘째는 누군가 메시지를 살짝 바꿔도 들통나게 만드는 무결성(integrity), 셋째는 통신 상대가 그 도메인의 진짜 주인임을 확인해 주는 인증(authentication)입니다. 이 세 가지가 자물쇠 아이콘이 약속하는 내용인 셈이죠.

다만 자주 오해되는 영역이 있습니다. HTTPS는 우리가 어떤 사이트에 접속했는지 자체를 숨겨 주지는 않는데요. SNI라는 정보가 핸드셰이크 초반에 평문으로 노출되어, 적어도 어떤 도메인에 접속하는지는 같은 회선 위의 관찰자가 알 수 있습니다. 또한 HTTPS는 “사이트가 안전하다”는 뜻이 아니라 “통신이 안전하다”는 뜻이라서, 자물쇠가 켜진 사이트라도 피싱 페이지일 수 있습니다. 누군가의 사기 사이트도 인증서만 정상적으로 발급받으면 자물쇠 표시는 켜집니다.

서버에 보낸 데이터를 서버가 어떻게 보관하는지, 응용 프로그램에 버그가 없는지도 HTTPS의 책임이 아닙니다. HTTPS는 문 앞에 자물쇠를 채워 줄 뿐, 집 안에서 일어나는 일까지 막아주지는 않는다고 생각하면 쉽습니다.

TLS 1.2와 1.3, 더 빨라진 핸드셰이크

TLS도 HTTP처럼 버전이 진화해 왔습니다. 오랫동안 표준이었던 TLS 1.2는 안전하지만, 핸드셰이크에 왕복이 두 번 필요해서 첫 응답이 도착하기까지 약간의 지연이 있었는데요. TLS 1.3은 이 절차를 한 번의 왕복으로 줄여, 첫 요청까지 걸리는 시간을 의미 있게 단축했습니다.

또 TLS 1.3은 안전하다고 검증되지 않은 옛 알고리즘들을 명세에서 아예 빼 버렸습니다. RC4, MD5, SHA-1 같은 약한 도구를 더 이상 협상 옵션에 두지 않아, 잘못된 설정으로 보안이 약해질 여지를 줄였죠. RFC 8446으로 2018년에 표준이 되었고, 요즘은 주요 브라우저와 서버 라이브러리가 모두 기본 지원합니다.

이전 세션 정보를 가지고 핸드셰이크 자체를 건너뛰는 0-RTT(zero round trip time) 모드도 함께 도입되었는데, 이 모드는 일부 재전송 공격에 약하다는 한계가 있어 사용처는 신중하게 정해야 합니다. 일반적인 사이트라면 1-RTT만으로도 충분히 빠르고 안전합니다.

HTTPS의 비용, 그리고 거의 사라진 비용

예전에는 “HTTPS는 느리다”는 이야기가 자주 나왔습니다. 암호화 연산이 추가되니 CPU를 더 쓰고, 핸드셰이크 때문에 첫 응답이 늦어지는 것이 사실이었거든요. 하지만 요즘 하드웨어는 AES 같은 대칭 암호 연산을 전용 명령어로 처리해서, 보통의 트래픽에서는 체감하기 어려울 만큼 비용이 작습니다.

오히려 HTTPS는 성능에 도움이 되는 면도 있습니다. HTTP/2와 HTTP/3은 사실상 HTTPS 위에서만 동작하기 때문에, HTTPS로 옮기지 않으면 새 버전의 이점을 받지 못하는데요. 브라우저가 HTTP/1.1로 떨어지면 한 페이지를 그리는 데 더 많은 연결을 써야 하니, 결과적으로는 HTTPS 쪽이 더 빨리 그려지는 경우도 흔합니다.

여기에 검색 엔진은 HTTPS를 사용하는 사이트를 살짝 우대하고, 브라우저는 HTTP 사이트에 “안전하지 않음”이라는 표시를 붙이기 시작한 지 오래되었습니다. 이제는 HTTPS를 쓰지 않을 이유를 찾는 쪽이 더 어려워진 셈입니다.

무료 인증서와 자동 갱신

HTTPS가 한때 비싸 보였던 또 다른 이유는 인증서 자체에 매년 비용이 들었기 때문입니다. 이 장벽을 거의 없앤 것이 Let’s Encrypt 같은 무료 인증 기관의 등장인데요. 도메인 소유를 자동으로 검증하는 ACME 프로토콜을 통해, 사람이 직접 손쓰지 않아도 인증서를 발급받고 갱신할 수 있게 되었습니다.

certbot으로 인증서 발급
sudo certbot --nginx -d example.com

위 명령어 한 줄이면 nginx 설정에 인증서를 자동으로 붙여 주고, 이후에는 cron이나 systemd timer가 갱신을 알아서 챙겨 주는데요. 요즘은 Caddy나 Traefik 같은 웹 서버가 처음부터 ACME 클라이언트를 내장하고 있어, 도메인만 가리키면 인증서까지 한 번에 처리해 줍니다. 처음 HTTPS를 붙여본 뒤 한 번 익숙해지면, 새 사이트를 만들 때 가장 먼저 챙기는 일 가운데 하나가 됩니다. Let’s Encrypt와 ACME가 어떻게 이 자동화를 가능하게 만들었는지가 더 궁금하다면 Let’s Encrypt 글에서 따로 정리해 두었습니다.

HSTS, 한 번 HTTPS면 끝까지 HTTPS

HTTPS를 켰다고 해서 안전이 모두 보장되는 것은 아닙니다. 공격자가 사용자의 첫 요청을 가로채 HTTP로 연결되도록 유도하면, 그 첫 통신은 여전히 평문으로 흐를 수 있는데요. 이런 SSL stripping 공격을 막기 위해 등장한 것이 HSTS(HTTP Strict Transport Security)입니다.

HSTS는 응답 헤더에 한 줄을 더하는 것으로 켤 수 있습니다.

HSTS 헤더 예시
Strict-Transport-Security: max-age=31536000; includeSubDomains

이 헤더를 한 번이라도 받은 브라우저는 지정된 기간 동안 해당 도메인에 대해 HTTP 요청을 보내지 않고, 어떤 링크가 http://로 적혀 있어도 자동으로 https://로 바꿔서 요청합니다. 첫 요청까지 안전하게 만들고 싶다면 HSTS preload 목록에 도메인을 등록할 수도 있는데, 이 목록은 브라우저에 직접 내장되어 있어 첫 방문부터 강제로 HTTPS만 사용하게 됩니다.

또 자주 마주치는 함정이 혼합 콘텐츠(mixed content)입니다. HTTPS 페이지 안에서 이미지나 스크립트를 http:// 주소로 불러오면, 브라우저가 그 자원을 차단하거나 경고를 띄우는데요. HTTPS로 옮길 때는 페이지 안의 모든 자원 URL을 함께 점검해야 깨끗하게 자물쇠가 켜집니다.

마치며

HTTPS는 새로운 프로토콜이라기보다 HTTP 위에 TLS라는 한 겹을 더한 형태입니다. 이 한 겹이 통신 내용을 가리고, 변조를 막고, 상대가 누구인지를 확인해 주는데요. TLS 1.3과 무료 인증서가 자리를 잡은 이후로는 도입 비용도 거의 사라져, “HTTPS는 기본”이라는 말이 더는 과장이 아닙니다.

다음에 자물쇠 아이콘을 보게 되면, 그 뒤에서 핸드셰이크와 인증서 체인이 어떤 일을 해주고 있는지를 한 번 떠올려보면 좋겠습니다. TLS의 자세한 동작이 궁금하다면 Cloudflare의 TLS 가이드를 추천합니다.

This work is licensed under CC BY 4.0 CC BY

개발자를 위한 뉴스레터

달레가 정리한 AI 개발 트렌드와 직접 만든 콘텐츠를 전해드립니다.

Discord