TLS 핸드셰이크의 비용 - 세션 재개와 0-RTT의 대가
HTTPS를 켜면 느려진다는 말은 절반만 맞다. 암호화 연산 자체는 현대 CPU에서 거의 공짜에 가깝고, 비용의 대부분은 연결을 맺을 때의 왕복에 있다. 그래서 대책도 암호화를 줄이는 것이 아니라 왕복을 줄이는 쪽이다.
비용이 어디에 있는가
TLS의 비용은 셋으로 나뉜다.
| 내용 | 크기 | |
|---|---|---|
| 왕복(RTT) | 핸드셰이크 메시지 교환 | RTT에 비례. 원거리일수록 지배적 |
| 비대칭 연산 | 키 교환과 인증서 서명 검증 | 연결당 한 번, 수 ms 이하 |
| 대칭 암복호 | 실제 데이터 암호화 | AES-NI 같은 하드웨어 가속으로 거의 무시 가능 |
첫 줄이 지배적이다. 서울-미국 RTT가 150ms라면, 핸드셰이크 왕복 하나가 150ms다. 암호 알고리즘을 바꿔서 줄일 수 있는 값이 아니다.
TLS 1.2와 1.3
TLS 1.2의 전체 핸드셰이크는 2-RTT다. ClientHello/ServerHello로 한 번, 키 교환과 Finished로 한 번. TCP 핸드셰이크 1-RTT까지 더하면 데이터가 나가기까지 3-RTT다.
TLS 1.3은 이것을 1-RTT로 줄였다. 클라이언트가 ClientHello에 키 공유 추정값을 미리 실어 보내고, 서버가 ServerHello에서 바로 확정한다. 암호 스위트 협상도 단순해졌다(취약한 옵션을 대거 제거했다).
TLS 1.3으로 올리는 것이 성능 측면에서 가장 확실한 조치다. 보안과 지연이 같은 방향으로 개선되는 드문 경우다.
세션 재개
이미 한 번 연결한 적 있는 클라이언트와 다시 연결할 때, 전체 핸드셰이크를 반복할 필요가 없다.
세션 ID. 서버가 세션 상태를 저장하고 ID로 참조한다. 서버가 여러 대면 그 상태를 공유해야 하고, 로드 밸런서가 다른 서버로 보내면 재개가 실패한다.
세션 티켓. 서버가 상태를 암호화해 클라이언트에게 준다. 서버는 상태를 저장하지 않는다. 서버가 여러 대여도 티켓 키만 공유하면 되므로 운영이 단순하다. TLS 1.3은 이 방식(PSK)으로 통일했다.
재개하면 1-RTT, 그리고 0-RTT까지 갈 수 있다.
0-RTT의 대가
TLS 1.3의 0-RTT는 재개 시 핸드셰이크 완료 전에 애플리케이션 데이터를 함께 보낸다. 왕복이 0이 되므로 지연 면에서는 최선이다.
문제는 재전송 공격(replay) 이다. 0-RTT 데이터는 핸드셰이크가 끝나기 전에 보내지므로 신선도를 보장하는 수단이 약하다. 공격자가 그 패킷을 가로채 다시 보내면 서버가 같은 요청을 두 번 처리할 수 있다.
그래서 규칙이 하나다. 0-RTT는 멱등한 요청에만 쓴다. GET 같은 안전한 요청은 괜찮고, 결제나 주문 생성은 안 된다(멱등한 API 설계). 실무에서는 CDN이나 엣지에서 0-RTT를 켜되 안전한 메서드로 제한하는 구성이 쓰인다.
QUIC(HTTP/3)의 0-RTT도 같은 제약을 갖는다(HTTP/1.1, 2, 3).
줄이는 순서
지연을 줄이는 조치를 효과 순으로 놓으면 이렇다.
- 연결을 재사용한다. keep-alive와 커넥션 풀. 핸드셰이크 자체가 일어나지 않으므로 가장 확실하다(TCP 연결 수립과 큐).
- TLS 1.3을 쓴다. 2-RTT가 1-RTT가 된다.
- 세션 재개를 켠다. 티켓 방식으로, 서버 간 키를 공유한다.
- 인증서 체인을 짧게. 체인이 길면 전송량과 검증 비용이 는다. 중간 인증서를 빠뜨리면 클라이언트가 추가로 받아와야 해 왕복이 더 생긴다.
- OCSP stapling. 인증서 폐기 확인을 클라이언트가 직접 하지 않고 서버가 응답을 붙여 준다. 별도 왕복이 사라진다.
- TLS 종료 지점을 사용자 가까이. CDN이나 엣지에서 종료하면 핸드셰이크 RTT가 짧아진다. 엣지-오리진 구간은 재사용되는 연결이라 비용이 분산된다.
1번이 압도적으로 중요하다. 연결당 한 번인 비용은 연결을 오래 쓰면 0에 수렴한다.
이 설명이 깨지는 곳
- 내부 서비스 간 mTLS는 계산이 다르다. 양쪽이 인증서를 검증하므로 비용이 늘고, 대신 RTT가 작아 왕복 비용은 작다. 사이드카 프록시가 연결을 재사용하면 대부분 상쇄된다.
- zero-copy가 깨진다. 암호화를 위해 데이터가 사용자 공간을 지나야 하므로
sendfile()최적화를 쓸 수 없다(Kafka의 저장 구조). 대용량 전송이 많은 시스템에서는 이 비용이 보인다. - 인증서 갱신이 운영 부담이다. 만료로 인한 장애는 흔하고, 자동 갱신과 만료 경보가 실질적인 대책이다.
- 암호 스위트를 임의로 조정하지 않는다. 성능을 이유로 약한 옵션을 켜면 보안이 깎인다. TLS 1.3은 선택지를 줄여 이 실수를 막았다.
무엇을 재면 확인되는가
- 연결을 매번 새로 여는 클라이언트와 keep-alive를 쓰는 클라이언트의 p50·p99를 비교한다. 차이가 곧 핸드셰이크 비용이다.
- TLS 1.2와 1.3에서 첫 바이트까지의 시간(TTFB)을 비교한다. RTT가 큰 경로일수록 차이가 크다.
- 세션 재개율을 지표로 본다. 낮으면 티켓 키가 서버 간에 공유되지 않고 있을 수 있다.
openssl s_client -connect ... -tls1_3로 핸드셰이크 왕복과 재개 여부를 직접 확인한다.
3번이 실무에서 자주 놓치는 부분이다. 세션 재개를 켰다고 생각했는데 로드 밸런싱 때문에 실제 재개율이 낮은 경우가 있다.
실무와의 접점
SSL/TLS 정리에서 프로토콜의 동작을 다뤘다. 지금 정리하면 실무에서 실제로 손대는 것은 알고리즘이 아니라 연결 수명과 종료 지점이었다. 성능 문제로 HTTPS를 의심하게 되면, 암호화가 아니라 “연결을 몇 번 맺고 있는가”부터 확인하는 것이 순서다.
정리
- TLS 비용의 대부분은 암호 연산이 아니라 핸드셰이크 왕복이다. RTT가 클수록 지배적이다.
- TLS 1.3은 전체 핸드셰이크를 2-RTT에서 1-RTT로 줄였다. 보안과 지연이 같이 개선된다.
- 세션 재개는 티켓 방식이 운영상 단순하다. 서버 간 티켓 키 공유가 전제다.
- 0-RTT는 재전송 공격에 취약하므로 멱등한 요청에만 쓴다.
- 가장 확실한 조치는 연결 재사용이다. 연결당 한 번인 비용은 오래 쓰면 0에 수렴한다.
- TLS를 켜면
sendfile()같은 zero-copy 최적화를 쓸 수 없다.
댓글
아직 댓글이 없습니다