지난 글에서 TLS 핸드셰이크의 전체 흐름을 훑었는데요, 그때 "키 교환" 단계는 한 줄로 지나갔습니다. 사실 이 키 교환이야말로 TLS에서 가장 짜릿한 부분입니다. 서로 한 번도 만난 적 없는 두 사람이, 누가 옆에서 패킷을 다 보고 있는 상황에서, 어떻게 둘만 아는 비밀 키를 만들어낼까요? 마술 같지만 수학이 만든 일입니다.
저도 처음 이 원리를 이해했을 때 "아니 이게 진짜 되네?" 싶었어요. 오늘은 TLS 안에서 대칭키와 비대칭키가 어떻게 역할을 나눠 가지는지, 그리고 실제 키 교환이 어떤 식으로 이뤄지는지 한 단계씩 풀어보겠습니다.
개념 / 원리
대칭키와 비대칭키, 빠른 비교
| 항목 | 대칭키 | 비대칭키 |
|---|---|---|
| 키 개수 | 1개 (양쪽 공유) | 2개 (공개키 + 개인키) |
| 속도 | 매우 빠름 | 일반적으로 수백 배 느림 |
| 약점 | 키를 어떻게 안전하게 나눠 갖나? | 느려서 대용량 통신엔 부적합 |
| 대표 알고리즘 | AES, ChaCha20 | RSA, ECDSA, ECDH |
이걸 보면 답이 보입니다. 둘은 경쟁자가 아니라 분업 관계예요.
TLS의 하이브리드 전략
TLS는 둘을 똑똑하게 섞어 씁니다.
- 비대칭키: 처음에 "세션 키"를 안전하게 만들거나 교환하는 데만 사용.
- 대칭키: 일단 세션 키가 만들어지면, 그 뒤 실제 데이터는 전부 대칭키로 암호화.
비유하자면 비대칭키는 "금고 열쇠를 안전하게 전달하는 봉투"이고, 대칭키는 "그 금고로 매일 빠르게 물건을 주고받는 도구"입니다. 봉투는 한 번만 쓰고, 일상 작업은 빠른 도구로.
키 교환의 두 가지 방식
TLS 키 교환은 크게 두 갈래로 발전해 왔습니다.
- RSA 키 교환 (옛날 방식, TLS 1.2까지): 클라이언트가 "pre-master secret"을 생성 → 서버의 RSA 공개키로 암호화 → 서버에 전송. 서버만 자기 개인키로 풀 수 있음.
- (EC)DHE 키 교환 (현대 방식, TLS 1.3 필수): 클라이언트와 서버가 각자 임시 키 쌍을 만들고 공개값만 교환. 수학적 트릭으로 양쪽이 동일한 비밀 값을 도출. 개인키가 유출돼도 과거 트래픽 복호화 불가(Forward Secrecy).
RSA 방식은 서버 개인키가 한 번 유출되면 그동안 캡처돼 있던 모든 트래픽이 복호화됩니다. 그래서 TLS 1.3은 RSA 키 교환을 아예 제거했어요.
실전 예제
1) ECDHE 키 교환, 한 문단으로
ECDHE를 풀면 이렇습니다.
1. 양쪽이 같은 타원곡선 파라미터 G에 합의 (Cipher suite에 들어 있음)
2. 클라이언트: 난수 a 생성 → 공개값 A = a × G 계산 → A 전송
3. 서버: 난수 b 생성 → 공개값 B = b × G 계산 → B 전송
4. 클라이언트: a × B 계산 = a × b × G (공유 비밀)
5. 서버: b × A 계산 = b × a × G (같은 값!)
6. 이 값으로 KDF를 돌려 세션 키(AES 키 등) 생성핵심은 A와 B는 공개돼도 a, b를 역으로 알아내기가 (실용적으로) 불가능하다는 점입니다. 이게 타원곡선 이산로그 문제예요. 도청자는 A, B를 다 보더라도 a × b × G는 못 만듭니다.
2) 협상된 키 교환 방식 확인
내가 접속한 서버가 어떤 키 교환을 쓰는지 openssl s_client로 바로 볼 수 있습니다.
openssl s_client -connect www.google.com:443 -servername www.google.com </dev/null 2>&1 \
| grep -E "Cipher|Server Temp Key|Protocol"
출력 예시는 보통 이렇게 나옵니다.
Protocol : TLSv1.3
Cipher : TLS_AES_256_GCM_SHA384
Server Temp Key: X25519, 253 bitsTLS_AES_256_GCM_SHA384: 대칭키 알고리즘은 AES-256-GCM.Server Temp Key: X25519: 키 교환은 X25519 곡선 기반 ECDHE. "Temp"라는 단어가 임시 키 쌍(=ephemeral=Forward Secrecy)이라는 의미입니다.
3) 서버가 지원하는 키 교환 점검
nmap --script ssl-enum-ciphers -p 443 www.google.com
출력 안에 cipher마다 키 교환 방식이 표시됩니다. ecdh_x25519, ecdh_secp256r1 같은 게 보이면 ECDHE 기반이라는 뜻이에요. 만약 키 교환 컬럼에 rsa가 떡하니 있다면 PFS가 깨지는 옛날 cipher가 살아 있다는 신호.
4) nginx에서 ECDHE만 허용하기
운영 서버라면 설정 한 줄로 확실히 못 박을 수 있습니다.
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers on;
ssl_ciphers ECDHE+AESGCM:ECDHE+CHACHA20;
ssl_ecdh_curve X25519:secp384r1:prime256v1;
ssl_ciphers에서 ECDHE만 남기면 RSA 키 교환은 자연스레 차단됩니다.
자주 하는 실수
- "RSA 인증서니까 RSA 키 교환"이라고 오해: 인증서 알고리즘과 키 교환 알고리즘은 별개입니다. RSA 인증서를 써도 ECDHE 키 교환은 얼마든지 가능해요. 인증서는 "이 서버가 진짜인지"를 증명하는 신원증, 키 교환은 그것과 별개로 매 접속마다 새로 합니다.
- Forward Secrecy를 끄고 운영: 옛날 클라이언트 호환성 때문에
ssl_ciphers에AES128-SHA같은 비(非)ECDHE 항목을 넣어두는 경우가 있습니다. 그러면 서버 개인키가 한 번 유출되는 순간 과거 트래픽이 다 풀립니다. - 세션 키와 인증서 개인키 혼동: 세션 키는 매 접속마다 새로 만들어지고 잠깐 살다 사라집니다. 인증서 개인키는 1년·2년 그대로죠. 둘은 수명도 역할도 완전히 다릅니다.
- 곡선 선택을 기본값에 방치: 최신 OpenSSL에서는
X25519가 기본인데, 옛날 OpenJDK·라이브러리는secp256r1만 잡고 있을 수 있습니다. 모바일이나 임베디드에서 호환성 문제가 터지면 곡선 목록부터 확인. - CDN 뒤 origin 점검 누락: CDN 뒤에서는 클라이언트가 보는 cipher가 CDN과 협상한 것이지 origin 서버와 협상한 게 아닙니다. origin까지 PFS인지 따로 확인이 필요해요.
저는 예전에 한 시스템에서 "트래픽 복호화가 가능해야 한다"는 요구가 있어서 RSA 키 교환을 일부러 켜둔 적이 있는데, 보안 점검에서 그 자리에서 지적당했습니다. 복호화가 정말 필요하면 차라리 정식 TLS 인터셉트 장비를 두는 게 맞아요.
마무리
정리하면 세 줄입니다.
- 대칭키는 빠르고 비대칭키는 안전한 키 분배가 가능, TLS는 둘을 섞어 쓴다.
- 현대 TLS는 ECDHE로 매 접속마다 임시 키 쌍을 만들고, 공개값만 교환해 같은 비밀을 도출한다 (Forward Secrecy).
- 운영에서는
Server Temp Key가 잡히는지, cipher 목록에 RSA 키 교환이 안 섞여 있는지를 보면 된다.
'보안' 카테고리의 다른 글
| [보안] Let's Encrypt와 ACME 프로토콜 - 인증서 자동 발급과 갱신의 동작 원리 (0) | 2026.05.21 |
|---|---|
| [보안] 인증서 검증의 전 과정 - 체인 추적부터 OCSP·CRL 폐기 확인까지 (0) | 2026.05.20 |
| [보안] HTTPS와 TLS 핸드셰이크 - 브라우저 자물쇠 안에서 벌어지는 일 (0) | 2026.05.18 |
| [보안] JWT 보안 - 흔히 하는 실수 5가지 (0) | 2026.05.14 |
| [보안] SQL Injection - 진짜 위험한 이유와 막는 법 (2) | 2026.05.04 |