이 사이트의 실습은 자바스크립트를 켜야 움직입니다. 아래는 같은 내용을 글로 적은 것입니다.
공부 › 네트워크 › 보안 연결
TLS 핸드셰이크
https의 자물쇠가 잠기기까지
HTTPS는 HTTP를 TLS로 감싼 것입니다. 데이터를 보내기 전에 브라우저와 서버는 "상대가 진짜 그 서버인지" 확인하고 "둘만 아는 암호화 키"를 만듭니다.
실행으로 대조 · openssl로 실제 TLS 1.3 연결을 맺어 확인
이 실습에 나오는 개념
- HTTPS와 TLS
- HTTPS는 HTTP 통신을 TLS로 암호화한 것이다. TLS는 통신 내용을 제3자가 볼 수 없게 하고(기밀성), 중간에서 바꿀 수 없게 하며(무결성), 접속한 서버가 진짜인지 인증서로 확인한다(인증). SSL은 TLS의 전신인 프로토콜로, 지금은 폐기되어 실제로는 TLS만 쓰이지만 관습적으로 SSL이라는 이름이 함께 불린다.
- TLS 핸드셰이크와 인증서
- TLS 핸드셰이크는 암호화 통신을 시작하기 전에 클라이언트와 서버가 사용할 암호 방식을 정하고, 서버의 신원을 확인하며, 데이터를 암호화할 키를 합의하는 과정이다. 인증서는 서버의 공개키와 도메인 정보를 신뢰할 수 있는 인증 기관(CA)이 서명해 보증한 문서로, 클라이언트는 이를 검증해 접속한 서버가 진짜임을 확인한다.
- 대칭키 암호화와 비대칭키 암호화
- 대칭키 암호화는 암호화와 복호화에 같은 키를 쓰는 방식으로 빠르지만 키를 상대와 안전하게 공유해야 하는 문제가 있다(AES). 비대칭키 암호화는 공개키와 개인키의 쌍을 쓰는 방식으로, 공개키로 암호화한 것은 개인키로만 풀 수 있어 키 공유 문제가 없지만 느리다(RSA). 실제로는 비대칭키로 대칭키를 안전하게 교환한 뒤 대칭키로 데이터를 암호화한다.
- HMAC과 전자서명
- 메시지가 중간에 바뀌지 않았고 정당한 상대가 보냈음을 확인하는 기술이다. HMAC은 양쪽이 공유한 비밀키와 메시지를 함께 해시해 인증 코드를 만든다. 전자서명은 보내는 쪽이 개인키로 서명하고 받는 쪽이 공개키로 검증하며, 서명한 쪽만 만들 수 있으므로 보낸 사실을 부인할 수 없다.
이렇게 바꿔 보세요
- 처음 설정으로 — 정상적인 TLS 1.3 핸드셰이크입니다.
- 인증서가 만료됐다면 — 브라우저가 경고를 띄우고 연결을 멈춥니다.
- 도메인이 다르다면 — 인증서는 진짜지만 이 서버의 것이 아닙니다.
- 자체 서명 인증서 — 누구도 보증해 주지 않는 인증서입니다.
처음 설정으로 실행하면
- 브라우저가 https://shop.example에 접속합니다. TCP 연결이 맺어진 뒤, 자신이 쓸 수 있는 암호 방식 목록과 키 합의에 쓸 공개값을 보냅니다. 이 메시지는 암호화되지 않았습니다.
- 서버가 암호 방식을 하나 고르고 자신의 키 합의용 공개값을 보냅니다. 양쪽은 서로의 공개값과 자신의 비밀값을 조합해 같은 값을 계산해 냅니다. 중간에서 공개값만 엿본 사람은 이 값을 계산할 수 없습니다.
- 양쪽이 계산한 같은 값에서 대칭키를 만들어 냅니다. 키 자체는 네트워크로 한 번도 전송되지 않았습니다. 이후의 핸드셰이크 메시지부터 암호화됩니다.
- 서버가 인증서를 보냅니다. 인증서에는 도메인 이름, 서버의 공개키, 유효 기간, 그리고 이 내용을 인증 기관(CA)이 자신의 개인키로 서명한 값이 들어 있습니다.
- 브라우저가 인증서를 검증합니다. 신뢰하는 인증 기관의 서명이 맞고, 유효 기간 안이며, 도메인이 shop.example과 일치합니다. 이 공개키는 진짜 shop.example의 것이라고 믿을 수 있습니다.
- 서버가 지금까지 주고받은 내용을 자신의 개인키로 서명해 보냅니다. 브라우저는 인증서의 공개키로 이 서명을 확인합니다. 인증서는 누구나 복사할 수 있지만, 서명은 짝이 되는 개인키를 가진 진짜 서버만 만들 수 있습니다.
- 양쪽이 핸드셰이크 내용 전체의 확인값을 주고받아, 중간에 누가 메시지를 바꾸지 않았는지 확인합니다.
- 이제 HTTP 요청과 응답이 합의한 대칭키로 암호화되어 오갑니다. 중간에서 보는 사람에게는 어느 서버와 통신하는지 정도만 보이고 내용(주소의 경로, 헤더, 쿠키, 본문)은 보이지 않습니다.
실행 결과
- 비대칭키(공개키와 개인키)는 "상대가 진짜인지 확인"하는 서명과 "키를 안전하게 합의"하는 데만 쓰입니다. 계산이 느리기 때문입니다. 실제 데이터는 빠른 대칭키로 암호화합니다. 두 방식의 장점을 나눠 쓰는 구조입니다.
- TLS 1.3은 이 과정을 왕복 한 번으로 끝냅니다(이전 버전은 두 번). 그래도 연결마다 비용이 들기 때문에 한 번 맺은 연결을 재사용하는 것이 중요합니다.
코드
# application.yml (서버 쪽 설정)
server:
port: 443
ssl:
enabled: true
key-store: classpath:shop.p12 # 인증서 + 개인키
key-store-password: ********
protocol: TLS
enabled-protocols: TLSv1.3
# 실무에서는 보통 앞단의 로드밸런서나 nginx가 TLS를 처리하고
# 애플리케이션 서버는 그 뒤에서 HTTP로 받는다
이 모형의 전제
TLS 1.3의 기본 핸드셰이크를 단순화했습니다(실제로는 서버의 응답 메시지 여러 개가 한 번에 묶여 전송됩니다). 키 합의는 양쪽이 공개값을 교환해 같은 비밀을 계산하는 방식(ECDHE)이며, 예전 방식처럼 대칭키를 서버 공개키로 암호화해 보내지 않습니다. 인증서 검증 항목은 TLS와 웹 PKI의 표준 절차를 따랐습니다. 이 PC에서 openssl로 시험용 인증 기관과 인증서 네 가지를 만들어 실제 TLS 1.3 연결을 맺어 확인했습니다. 정상 인증서는 검증을 통과했고, 나머지는 각각 certificate has expired, hostname mismatch, self-signed certificate 오류로 거부됐습니다. 핸드셰이크 메시지도 ClientHello, ServerHello, EncryptedExtensions, Certificate, CertificateVerify, Finished 순서로 오가는 것을 확인했습니다.
앞 실습: 폴링·SSE·WebSocket · 다음 실습: 로드밸런서와 스케일 아웃