HTTPS 동작 원리: RSA 키 교환부터 ECDHE, MITM

김민호·5일 전

"HTTPS는 어떻게 동작할까?"라는 질문을 파고들다 보니 생각보다 엮여 있는 개념이 많았습니다. 이번 글에서는 제가 공부하면서 헷갈렸던 순서를 그대로 따라가 보려고 합니다.

처음에는 "공개키로 암호화하면 되는 것 아닌가?"라고 생각했습니다. 그런데 곧 "그 공개키는 어떻게 믿을 수 있지?"라는 의문이 생겼고, 이어서 "중간에 누군가 끼어들면 어떻게 되지?"라는 질문으로 넘어가게 되었습니다. 이 흐름대로 하나씩 정리해 보겠습니다.


1. HTTP는 왜 위험할까

HTTP는 데이터를 평문 그대로 전송합니다. 클라이언트와 서버 사이에는 공유기, ISP, 라우터 같은 장비가 수없이 많은데, 그중 어느 지점에서든 패킷을 들여다볼 수 있다면 다음과 같은 문제가 생깁니다.

문제설명
도청비밀번호나 카드번호를 그대로 읽을 수 있습니다
변조중간에서 내용을 바꿔치기할 수 있습니다
사칭접속한 서버가 진짜인지 확인할 수 없습니다

이 문제를 해결하기 위해 HTTP를 TLS로 감싼 것이 HTTPS입니다. 참고로 SSL은 TLS의 이전 이름입니다. SSL은 보안 취약점 때문에 더 이상 사용되지 않고, 현재는 TLS 1.2와 TLS 1.3이 주로 쓰이고 있습니다.


2. 먼저 알아야 할 개념: 대칭키와 비대칭키

대칭키

대칭키 방식은 암호화할 때와 복호화할 때 같은 키를 사용합니다. 속도가 빠르다는 장점이 있지만, 이 키를 상대방에게 어떻게 안전하게 전달할 것인가라는 문제가 남습니다.

비대칭키 (공개키 / 개인키)

비대칭키 방식은 키가 한 쌍으로 이루어져 있습니다. 공개키는 누구에게나 공개해도 되고, 개인키는 주인만 보관합니다. 쓰임새는 크게 두 가지입니다.

  • 암호화: 공개키로 암호화한 데이터는 개인키로만 복호화할 수 있습니다.
  • 서명: 개인키로 서명한 데이터는 공개키로 검증할 수 있고, 이를 통해 실제 주인이 서명했다는 사실을 확인할 수 있습니다.

다만 대칭키에 비해 연산이 느립니다.

그래서 TLS는 두 방식을 함께 사용합니다. 비대칭키로 안전한 연결을 준비하고, 실제 데이터는 빠른 대칭키(세션 키)로 주고받는 구조입니다.


3. 처음 떠올린 방법: RSA 키 교환 (TLS 1.2까지)

가장 직관적인 방법은 다음과 같습니다.

  1. 서버는 통신을 시작하기 전에 공개키와 개인키를 미리 만들어 둡니다. 공개키는 암호화에, 개인키는 복호화에 사용합니다.
  2. 클라이언트는 세션 키(대칭키)를 생성합니다. 이 키 하나로 암호화와 복호화를 모두 처리합니다.
  3. 클라이언트는 서버로부터 공개키를 받습니다.
  4. 받은 공개키로 세션 키를 암호화해서 서버에 보냅니다.
  5. 서버는 개인키로 이를 복호화해 세션 키를 얻습니다.
  6. 이제 양쪽은 같은 세션 키로 데이터를 암호화해 보내고, 받은 쪽에서 복호화합니다.
  Client                                           Server
    |                                                 |  (1) generate key pair
    |  (2) generate session key                       |
    |                                                 |
    |<----------------------- (3) server public key --|
    |                                                 |
    |-- (4) Enc(public key, session key) ------------>|
    |                                                 |  (5) Dec(private key)
    |                                                 |      -> session key
    |                                                 |
    |<======= (6) encrypted with session key ========>|

누군가 중간에서 엿보더라도 암호화된 세션 키만 보일 뿐이고, 개인키가 없으니 풀 수 없습니다. 언뜻 보면 빈틈이 없어 보입니다.

참고로 실제 TLS에서는 세션 키를 그대로 보내지 않습니다. pre-master secret이라는 비밀값을 보낸 뒤, 양쪽이 이 값을 바탕으로 세션 키를 만들어 냅니다. 하지만 공개키로 암호화해서 보내고 개인키로 푼다는 원리 자체는 동일합니다.

문제는 3번 단계에 있습니다.


4. 그 공개키는 정말 서버의 것일까

클라이언트에게는 받은 공개키가 진짜 서버의 것인지 확인할 방법이 없습니다. 바로 이 지점에서 MITM(Man-In-The-Middle), 즉 중간자 공격이 가능해집니다.

MITM 공격 시나리오

공격자가 공용 와이파이 같은 환경에서 클라이언트와 서버 사이에 끼어들었다고 가정해 보겠습니다.

  Client                    Attacker                  Server
    |                         |                         |
    |                         |<------ server pub ------|
    |<----- attacker pub -----|                         |
    |                         |                         |  (1) swap public key
    |                         |                         |
    |-- Enc(attacker pub, --->|                         |
    |    session key)         |                         |
    |                         | Dec(attacker priv)      |  (2)(3)
    |                         | -> session key!         |
    |                         |                         |
    |                         |--- Enc(server pub, ---->|
    |                         |    session key)         |  (4)
    |                         |                         |
    |<========== reads & modifies everything ==========>|
  1. 공격자는 서버의 공개키를 가로채고, 자신의 공개키를 서버의 것처럼 클라이언트에게 전달합니다.
  2. 클라이언트는 이를 모른 채 공격자의 공개키로 세션 키를 암호화합니다.
  3. 공격자는 자신의 개인키로 이를 복호화해 세션 키를 손에 넣습니다.
  4. 그런 다음 서버의 공개키로 다시 암호화해서 서버에 넘깁니다.

클라이언트와 서버는 모두 정상적으로 통신하고 있다고 믿지만, 실제로는 공격자가 대화 내용을 모두 읽을 수 있고 필요하면 바꿀 수도 있습니다.

여기서 주목할 점은 암호화 자체에는 아무 문제가 없었다는 것입니다. 빠져 있던 것은 "지금 누구와 암호화된 통신을 하고 있는가"에 대한 확인이었습니다.


5. 해결책: CA와 인증서

그렇다면 신뢰할 수 있는 제3자가 "이 공개키는 naver.com의 것이 맞다"고 보증해 주면 됩니다. 이 역할을 하는 기관이 CA(Certificate Authority, 인증기관)입니다.

5-1. 인증서 발급 과정

  1. 서버가 키 쌍을 직접 생성합니다. 개인키는 절대 서버 밖으로 나가지 않아야 합니다. CA가 대신 만들어 준다면 CA도 개인키를 알게 되기 때문입니다.
  2. 서버는 공개키와 도메인 정보를 담은 요청서(CSR)를 CA에 제출합니다.
  3. CA(정확히는 등록 업무를 담당하는 RA)가 요청자가 실제 도메인 소유자인지 확인합니다.
  4. 확인이 끝나면 CA가 인증서를 만듭니다. 인증서 내용(도메인, 서버 공개키, 유효기간, 발급자 등)으로 해시값을 계산하고, 그 해시값에 CA의 개인키로 서명합니다.
  5. 인증서 내용과 CA의 서명을 묶어 서버에 발급합니다.

저는 처음에 "CA가 서버의 공개키를 암호화한다"고 잘못 이해하고 있었습니다. 실제로 서버의 공개키는 인증서 안에 그대로 노출되어 있습니다. CA가 하는 일은 암호화가 아니라 서명입니다. 내용을 숨기려는 것이 아니라, CA가 이 내용을 보증하며 중간에 변경되지 않았다는 사실을 증명하려는 것입니다.

5-2. 인증서의 구성

+--------------------------------------------+
|  Certificate (X.509)                       |
+--------------------------------------------+
|  Version       : v3                        |
|  Serial Number : 04:a1:9f:...              |
|  Issuer        : DigiCert ... CA           |
|  Validity      : 2026-01-01 ~ 2027-01-01   |
|  Subject       : www.naver.com             |
|  Public Key    : server public key         |
|  Extensions    : SAN: *.naver.com, ...     |
+--------------------------------------------+
|  Sig. Alg. : sha256WithRSAEncryption       |
|  Signature : Sign(CA private key,          |
|                   hash(contents above))    |
+--------------------------------------------+

5-3. 클라이언트의 인증서 검증

운영체제와 브라우저에는 신뢰할 수 있는 CA의 인증서(공개키)가 미리 내장되어 있습니다. 이를 루트 인증서 저장소라고 부릅니다. Microsoft, Apple, Google, Mozilla가 각자 기준을 정해 두고, 그 기준을 통과한 CA만 목록에 포함시킵니다.

검증은 다음 순서로 이루어집니다.

  1. 인증서 내용으로 해시값을 직접 계산합니다.
  2. 인증서에 첨부된 서명을 미리 가지고 있던 CA의 공개키로 검증합니다.
  3. 서명이 일치하면 CA가 보증한 내용이며 중간에 변조되지 않았다는 뜻입니다.
  4. 추가로 인증서의 도메인이 현재 접속하려는 주소와 같은지, 유효기간이 지나지 않았는지를 확인합니다.

실제로는 루트 CA가 서버 인증서에 직접 서명하지 않고, 루트 CA에서 중간 CA를 거쳐 서버 인증서로 서명이 이어집니다. 이를 인증서 체인이라고 하며, 브라우저는 이 체인을 따라 올라가 루트 저장소에 있는 CA까지 도달하는지 확인합니다.

5-4. 인증서를 그대로 복사하면 어떻게 될까

인증서는 공개된 정보이므로 공격자도 naver.com의 실제 인증서를 그대로 복사해 보여줄 수 있습니다. 하지만 공격자에게는 그 인증서에 담긴 공개키와 짝을 이루는 개인키가 없습니다.

  • RSA 키 교환에서는 클라이언트가 인증서 속 공개키로 세션 키를 암호화해 보냅니다. 개인키가 없는 공격자는 이를 복호화할 수 없습니다.
  • TLS 1.3에서는 서버가 핸드셰이크 내용에 자신의 개인키로 서명해서 보냅니다(CertificateVerify). 개인키가 없는 공격자는 이 서명을 만들어 낼 수 없습니다.

결국 인증서는 "이 공개키가 naver.com의 것"임을 확인해 주고, 개인키를 실제로 사용할 수 있는지가 "지금 통신하는 상대가 그 주인"임을 확인해 줍니다. 두 가지가 모두 갖춰져야 신원 확인이 완료됩니다.

5-5. MITM이 막히는 원리

공격자가 시도할 수 있는 방법은 크게 두 가지인데, 어느 쪽도 성공하지 못합니다.

공격자의 시도결과
자신의 공개키로 가짜 인증서를 만들어 제시신뢰할 수 있는 CA의 서명이 없거나 도메인이 일치하지 않아 브라우저가 경고를 표시합니다
실제 인증서를 복사해서 제시개인키가 없으므로 복호화도, 서명도 할 수 없습니다

6. 남은 문제: 개인키가 나중에 유출된다면

RSA 키 교환에는 또 하나의 약점이 있습니다. 서버의 개인키 하나만 있으면 모든 세션 키를 풀 수 있다는 점입니다.

공격자가 오늘 오가는 암호화된 통신을 모두 저장해 두었다가, 몇 년 뒤 서버의 개인키를 탈취했다고 생각해 보겠습니다. 그러면 저장해 둔 세션 키를 복호화할 수 있고, 결과적으로 과거의 대화 내용까지 전부 해독할 수 있게 됩니다.

이 문제를 해결하기 위해 등장한 방식이 ECDHE입니다.

6-1. ECDHE: 세션 키를 아예 보내지 않는 방식

ECDHE의 아이디어는 세션 키를 암호화해서 전송하는 대신, 양쪽이 각자 계산해서 같은 값을 얻도록 하자는 것입니다.

작은 숫자로 직접 계산해 보면 쉽게 이해할 수 있습니다. 아래 예시는 원조 격인 DH 방식이며, ECDHE는 거듭제곱 대신 타원곡선 연산을 사용한다는 차이만 있을 뿐 원리는 같습니다.

① 미리 공개된 숫자

표준으로 정해져 있어 브라우저와 서버 코드에 이미 포함되어 있는 값입니다.

  • g = 5, p = 23 (mod 23은 23으로 나눈 나머지를 의미합니다)

② 각자 비밀 숫자 선택 (절대 전송하지 않음)

  • 클라이언트: a = 6
  • 서버: b = 15

③ 공개값을 계산해서 교환

  • 클라이언트: 5⁶ mod 23 = 8 → 서버에 전송
  • 서버: 5¹⁵ mod 23 = 19 → 클라이언트에 전송

④ 받은 공개값에 자신의 비밀 숫자로 계산

  • 클라이언트: 19⁶ mod 23 = 2
  • 서버: 8¹⁵ mod 23 = 2

양쪽 모두 2라는 같은 값을 얻었습니다. 이것이 공유 비밀값입니다.

같은 값이 나오는 이유는 간단합니다. 클라이언트가 계산한 19⁶은 (5¹⁵)⁶, 즉 5^(15×6)이고, 서버가 계산한 8¹⁵는 (5⁶)¹⁵, 즉 5^(6×15)입니다. 곱하는 순서만 다를 뿐 둘 다 5^(a×b)를 계산한 셈입니다.

반면 중간에서 엿본 사람은 5, 23, 8, 19만 알고 있습니다. 공유 비밀값을 구하려면 "5를 몇 번 거듭제곱해야 8이 되는가"를 풀어야 하는데, 실제로는 수백 자리에 달하는 큰 수를 사용하기 때문에 현실적으로 풀 수 없습니다.

6-2. 전방 비밀성 (Forward Secrecy)

ECDHE의 E는 Ephemeral, 즉 "일회용"을 뜻합니다. 비밀 숫자는 연결할 때마다 새로 생성되고, 연결이 끝나면 폐기됩니다.

또한 서버의 개인키는 더 이상 세션 키 계산에 쓰이지 않고 신원 증명(서명)에만 사용됩니다. 따라서 나중에 개인키가 유출되더라도 과거의 대화는 안전하게 보호됩니다. 이 성질을 전방 비밀성이라고 합니다.

RSA 키 교환ECDHE
세션 키클라이언트가 생성해 암호화 후 전송전송하지 않고 양쪽이 각자 계산
서버 개인키의 역할세션 키 복호화신원 증명(서명)만 담당
개인키 유출 시과거 대화까지 모두 노출과거 대화는 안전
TLS 1.3제거됨유일하게 남은 방식

6-3. ECDHE만으로 MITM을 막을 수 있을까

그렇지 않습니다. ECDHE만으로는 중간자 공격을 막을 수 없습니다.

공격자가 클라이언트와는 자신의 공개값으로, 서버와는 또 다른 공개값으로 각각 키 교환을 진행하면 두 개의 연결을 따로 맺고 그 사이에서 중계할 수 있기 때문입니다. 4장에서 살펴본 공격과 구조가 똑같습니다.

그래서 TLS 1.3에서는 서버가 ECDHE 공개값이 포함된 핸드셰이크 전체에 개인키로 서명합니다. 공격자가 공개값을 바꿔치기하면 서명이 일치하지 않아 곧바로 드러나게 됩니다.

정리하자면 ECDHE는 도청을 막고, 인증서와 서명은 사칭을 막습니다. 안전한 통신을 위해서는 둘 다 필요합니다.


7. TLS 1.3 핸드셰이크 전체 흐름

  Client                                           Server
    |                                                 |
    |-- ClientHello --------------------------------->|
    |  random, key_share (client ECDHE pub)           |
    |                                                 |
    |<--------------------------------- ServerHello --|
    |  random, key_share (server ECDHE pub)           |
    |                                                 |
    |      [ both compute shared secret -> keys ]     |
    |                                                 |
    |<------------------------------- {Certificate} --|
    |<------------------------- {CertificateVerify} --|
    |<---------------------------------- {Finished} --|
    |                                                 |
    |-- {Finished} ---------------------------------->|
    |                                                 |
    |<============== application data ===============>|

  {...} = encrypted
단계하는 일
ClientHello / ServerHello사용할 방식을 협상하고, 랜덤값과 키 교환 재료를 주고받습니다
세션 키 계산각자 공유 비밀값을 계산한 뒤 랜덤값과 조합해 세션 키를 만듭니다
Certificate서버가 인증서를 보내고, 브라우저가 CA 체인을 따라 검증합니다
CertificateVerify서버가 개인키로 서명해 인증서의 실제 주인임을 증명합니다
Finished지금까지 주고받은 메시지가 변조되지 않았는지 서로 확인합니다

TLS 1.2에서는 핸드셰이크에 왕복이 두 번 필요했지만, TLS 1.3은 키 교환 재료를 첫 메시지에 바로 실어 보내면서 왕복 한 번(1-RTT)으로 줄였습니다. 세션 키가 더 일찍 만들어지기 때문에 인증서부터 암호화된 상태로 전송된다는 점도 달라진 부분입니다.

헷갈리기 쉬운 세 종류의 키

키생성 시점용도
ECDHE 비밀 숫자 / 공개값연결마다 새로 생성하고 폐기공유 비밀값 계산
서버 개인키 / 공개키서버가 미리 생성해 장기간 보관신원 증명(서명)
세션 키핸드셰이크 중 양쪽이 계산실제 데이터 암호화(대칭키)

8. 그럼에도 MITM이 성공하는 경우

TLS가 중간자 공격을 막아 주기는 하지만, 다음과 같은 상황에서는 뚫릴 수 있습니다.

사용자가 인증서 경고를 무시하는 경우
"이 연결은 안전하지 않습니다"라는 경고 화면에서 "계속 진행"을 누르면 공격자의 인증서를 신뢰하게 됩니다.

공격자의 루트 인증서가 PC에 설치된 경우
악성 프로그램이 루트 저장소에 가짜 CA를 등록하면, 그 CA로 서명한 인증서는 모두 검증을 통과합니다. 사실 회사의 보안 프록시가 HTTPS 트래픽을 검사하는 것도 이와 같은 방식입니다.

CA가 해킹당하거나 인증서를 잘못 발급한 경우
이에 대비해 인증서 폐기 목록(CRL, OCSP)과 발급 기록을 공개하는 Certificate Transparency 같은 장치가 마련되어 있습니다.

처음 접속을 HTTP로 하는 경우 (SSL Stripping)
사용자가 http://로 접속하는 순간, 공격자가 HTTPS로의 전환을 가로막고 평문으로 중계할 수 있습니다. 서버는 HSTS 헤더를 통해 "앞으로는 반드시 HTTPS로만 접속하라"고 브라우저에 알려 이를 방지합니다.


지금까지의 내용을 짧게 정리하면 다음과 같습니다.

  1. HTTP는 평문으로 통신하기 때문에 도청, 변조, 사칭에 취약합니다.
  2. 공개키로 세션 키를 암호화해 보내면 도청은 막을 수 있지만, 공개키의 진위를 확인하지 못하면 중간자 공격에 노출됩니다.
  3. 이를 위해 CA가 서버 공개키에 서명한 인증서로 공개키의 주인을 보증하고, 서버는 개인키 서명으로 자신이 그 주인임을 증명합니다.
  4. RSA 키 교환은 개인키가 유출되면 과거 대화까지 해독될 수 있어, TLS 1.3에서는 세션 키를 전송하지 않는 ECDHE만 남게 되었습니다.
  5. 결국 HTTPS는 비대칭키로 신원 확인과 키 합의를 수행하고, 실제 데이터는 대칭키인 세션 키로 빠르게 암호화하는 구조입니다.

처음에는 단순히 "암호화해서 보낸다" 정도로만 알고 있었는데, 막상 들여다보니 누구와 통신하는지 확인하는 과정이 훨씬 큰 비중을 차지하고 있었습니다. 같은 부분에서 헷갈리셨던 분들께 이 글이 조금이나마 도움이 되었으면 합니다.

profile
개발자를 꿈꾸고 있어요

0개의 댓글