icon

안동민 개발노트

10장 : HTTPS와 네트워크 보안

TLS 핸드셰이크


암호화의 기본 개념을 잡았으니, 이제 HTTPS 연결이 실제로 어떻게 수립되는지를 따라가 보겠습니다. 브라우저가 https:// URL에 접속할 때 TCP 3-way handshake 직후, 데이터를 교환하기 전에 TLS 핸드셰이크가 이루어집니다.


TLS 1.2 핸드셰이크 전체 흐름

TLS 1.2 핸드셰이크 시퀀스
클라이언트                                      서버
    │                                            │
    │── ① Client Hello ────────────────────────→│
    │   * TLS 버전: 1.2                          │
    │   * 지원 Cipher Suite 목록                 │
    │   * 클라이언트 랜덤 (28바이트)             │
    │                                            │
    │←─ ② Server Hello ─────────────────────────│
    │   * 선택된 TLS 버전: 1.2                   │
    │   * 선택된 Cipher Suite                    │
    │   * 서버 랜덤 (28바이트)                   │
    │                                            │
    │←─ ③ Certificate ──────────────────────────│
    │   * 서버 인증서 (공개키 포함)              │
    │   * 인증서 체인 (중간 CA 포함)             │
    │                                            │
    │←─ ④ Server Key Exchange ──────────────────│
    │   * ECDHE 파라미터 (곡선, 서버 공개값)     │
    │   * 파라미터에 대한 디지털 서명            │
    │                                            │
    │←─ ⑤ Server Hello Done ────────────────────│
    │                                            │
    │── ⑥ Client Key Exchange ─────────────────→│
    │   * 클라이언트 ECDHE 공개값                │
    │                                            │
    │   [양쪽: Pre-Master Secret 독립 계산]      │
    │   [양쪽: Master Secret 생성]               │
    │   [양쪽: 세션 키(대칭키) 생성]             │
    │                                            │
    │── ⑦ Change Cipher Spec ──────────────────→│
    │── ⑧ Finished (암호화됨) ─────────────────→│
    │                                            │
    │←─ ⑨ Change Cipher Spec ───────────────────│
    │←─ ⑩ Finished (암호화됨) ──────────────────│
    │                                            │
    │←═══════ 암호화된 통신 시작 ═══════════════→│

이 전체 과정에 2 RTT가 소요됩니다. TCP 핸드셰이크(1 RTT)까지 합하면 첫 데이터 전송까지 3 RTT가 필요합니다.

Cipher Suite 구조
TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
│     │      │       │    │    │    │
│     │      │       │    │    │    └── 해시: SHA-384
│     │      │       │    │    └─────── 암호 모드: GCM
│     │      │       │    └──────────── 키 길이: 256비트
│     │      │       └───────────────── 대칭 암호: AES
│     │      └───────────────────────── 인증: RSA
│     └──────────────────────────────── 키 교환: ECDHE
└────────────────────────────────────── 프로토콜: TLS
구성 요소역할TLS 1.2 옵션TLS 1.3 옵션
키 교환대칭키 교환RSA, DHE, ECDHEECDHE만
인증서버 신원RSA, ECDSARSA, ECDSA
대칭 암호데이터 암호화AES-CBC, AES-GCM...AES-GCM, ChaCha20
해시무결성SHA-1, SHA-256...SHA-256, SHA-384

디피-헬만 키 교환

TLS에서 가장 중요한 단계가 키 교환입니다. ECDHE(Elliptic Curve Diffie-Hellman Ephemeral)가 현재 표준입니다.

디피-헬만 키 교환 원리 (색 비유)
Alice와 Bob이 공개 채널에서 비밀 색(=키)을 합의하는 방법:

  공개 정보: 노란색 (누구나 앎)

  Alice:
    비밀색 = 빨간색
    노란색 + 빨간색 = 주황색 ──공개──→ Bob에게 전송

  Bob:
    비밀색 = 파란색
    노란색 + 파란색 = 초록색 ──공개──→ Alice에게 전송

  Alice:
    Bob의 초록색 + Alice의 빨간색 = 갈색 ✓

  Bob:
    Alice의 주황색 + Bob의 파란색 = 갈색 ✓

  도청자:
    주황색과 초록색만 봄
    노란색도 앎
    하지만 빨간색/파란색 없이 갈색을 만들 수 없음!

수학적으로
  Alice: g^a mod p → 공개
  Bob:   g^b mod p → 공개
  공유키: g^(a*b) mod p (양쪽 동일하게 계산 가능)
  공격자: g^a, g^b만으로 g^(a*b) 계산 불가 (이산 로그 문제)

ECDHE의 E(Ephemeral)는 매 연결마다 새로운 키 쌍을 사용한다는 뜻입니다. 이것이 전방 비밀성(Forward Secrecy)을 보장합니다.

전방 비밀성 (Forward Secrecy)
RSA 키 교환 (전방 비밀성 없음)
  서버의 RSA 개인키가 유출되면?
  → 과거에 녹화했던 모든 트래픽 복호화 가능!
  → 공격자가 트래픽을 미리 저장해뒀다면 전부 노출

ECDHE (전방 비밀성 있음)
  서버의 개인키가 유출되어도?
  → 각 세션의 임시 키는 이미 폐기됨
  → 과거 트래픽 복호화 불가!
  → "지금 뚫려도 과거는 안전"

TLS 1.3의 개선점

TLS 1.3은 2018년에 표준화되었으며, TLS 1.2에 비해 보안과 성능 모두에서 큰 개선이 이루어졌습니다.

TLS 1.3 핸드셰이크 (1 RTT)
클라이언트                                     서버
    │                                           │
    │── Client Hello ──────────────────────────→│
    │   + 지원 Cipher 목록                      │
    │   + 키 교환 파라미터 (미리 포함!)         │
    │   + SNI                                   │
    │                                           │
    │←─ Server Hello ───────────────────────────│
    │   + 선택된 Cipher                         │
    │   + 키 교환 파라미터                      │
    │   + 인증서 (암호화됨!)                    │
    │   + Finished                              │
    │                                           │
    │── Finished + HTTP 요청 ──────────────────→│
    │                                           │
    │   1 RTT만에 암호화 통신 시작!             │
변경 사항TLS 1.2TLS 1.3
핸드셰이크2 RTT1 RTT (0-RTT 재연결)
키 교환RSA, DHE, ECDHEECDHE만 (전방 비밀성 필수)
대칭 암호CBC/GCM 모두GCM/ChaCha20만 (AEAD 필수)
해시SHA-1 허용SHA-256 이상만
인증서평문 전송암호화 전송
압축지원 (CRIME 공격 원인)제거
재협상지원제거 (보안 취약)
tls_check.sh
# 서버의 TLS 버전 확인
openssl s_client -connect google.com:443 -tls1_3 2>&1 | grep "Protocol"
# Protocol  : TLSv1.3

# 서버가 지원하는 Cipher Suite 확인
openssl s_client -connect google.com:443 2>&1 | grep "Cipher"
# Cipher    : TLS_AES_256_GCM_SHA384

# 인증서 정보 확인
openssl s_client -connect google.com:443 2>&1 | openssl x509 -noout -subject -dates
# subject=CN = *.google.com
# notBefore=Nov 13 08:38:04 2023 GMT
# notAfter=Feb  5 08:38:03 2024 GMT

# 인증서 체인 전체 출력
openssl s_client -connect google.com:443 -showcerts 2>&1 | grep "s:"

인증서와 CA

TLS 핸드셰이크에서 서버가 보내는 인증서이 서버가 진짜 이 도메인의 소유자인지를 증명합니다.

인증서 구조
┌──────────────────────────────────────┐
│          X.509 인증서                │
├──────────────────────────────────────┤
│ 버전: v3                             │
│ 시리얼 번호: 04:A1:B2:...            │
│ 서명 알고리즘: sha256WithRSA         │
│ 발급자: CN=DigiCert Global CA        │
│ 유효기간: 2024-01-01 ~ 2025-01-01    │
│ 주체: CN=*.example.com               │
│ 공개키: RSA 2048비트                 │
│   (서버의 공개키가 여기에)           │
│ 확장:                                │
│   SAN: example.com, *.example.com    │
│   Key Usage: Digital Signature       │
├──────────────────────────────────────┤
│ CA의 디지털 서명                     │
│ (위 내용의 해시를 CA 개인키로 암호화)│
└──────────────────────────────────────┘

인증서 체인 검증

인증서 신뢰 체인
운영체제/브라우저에 미리 설치됨

┌─────────────────────────┐
│ Root CA 인증서          │  Trust Anchor
│ CN=DigiCert Root CA     │  자체 서명 (Self-signed)
│ 키: RSA 4096            │
└──────────┬──────────────┘
           │ 서명
┌──────────▼──────────────┐
│ Intermediate CA 인증서  │  중간 CA
│ CN=DigiCert Global CA   │  Root에 의해 서명됨
│ 키: RSA 2048            │
└──────────┬──────────────┘
           │ 서명
┌──────────▼──────────────┐
│ 서버 인증서 (Leaf)      │  최종 인증서
│ CN=*.example.com        │  중간 CA에 의해 서명됨
│ 키: RSA 2048            │
└─────────────────────────┘

브라우저 검증 순서
  1. 서버 인증서의 서명 → 중간 CA 공개키로 검증 ✓
  2. 중간 CA 인증서의 서명 → Root CA 공개키로 검증 ✓
  3. Root CA가 신뢰 저장소에 있는지 확인 ✓
  4. 도메인 일치 확인 (CN/SAN vs 접속 도메인) ✓
  5. 유효기간 확인 ✓
  6. 폐기 여부 확인 (CRL/OCSP) ✓
  → 모두 통과하면 자물쇠 아이콘 표시

왜 루트 CA가 직접 서명하지 않을까요? 루트 CA의 개인키는 오프라인 금고에 보관됩니다. 중간 CA의 키가 유출되면 그 중간 CA만 폐기하면 되지만, 루트 CA의 키가 유출되면 전체 신뢰 체계가 무너집니다.

다음 절에서는 실무에서 인증서를 관리하는 구체적인 방법을 살펴보겠습니다.

목차