HTTPS 사이트에 접속할 때 주소창에 자물쇠 아이콘이 뜨는 거, 다들 한 번쯤은 보셨을 거예요. 가끔 그 자물쇠가 깨지거나 "이 연결은 안전하지 않습니다" 경고가 뜨면 가슴이 철렁하죠. 저도 예전에 운영 중인 서비스의 인증서가 만료된 줄 모르고 있다가, 새벽에 고객사 전화 받고 부랴부랴 갱신한 적이 있습니다.
HTTPS가 보안 통신이라는 건 알겠는데, 실제로 그 자물쇠 안에서 무슨 일이 벌어지는지는 의외로 흐릿하게 아는 분들이 많습니다. 오늘은 HTTPS의 심장인 TLS 핸드셰이크가 정확히 어떤 순서로, 무엇을 주고받는지 한번 제대로 까보겠습니다.
개념 / 원리
HTTPS는 사실 새로운 프로토콜이 아니라 HTTP + TLS 조합입니다. 평문 HTTP를 TLS라는 보안 계층 위에 얹어 돌리는 거죠. 그래서 핵심은 TLS인데요, TLS가 하는 일은 크게 세 가지입니다.
- 암호화(Encryption): 중간에서 누가 패킷을 보더라도 내용을 못 읽게.
- 인증(Authentication): 내가 접속한 서버가 진짜 그 서버인지 확인.
- 무결성(Integrity): 전송 중에 누가 메시지를 바꿔치기하지 못하게.
이 세 가지를 보장하려면 클라이언트와 서버가 먼저 "어떤 암호 알고리즘 쓸지", "키는 어떻게 만들지", "서버 신원은 어떻게 확인할지"를 합의해야 합니다. 이 합의 과정이 바로 핸드셰이크예요.
비유하자면 처음 만난 두 사람이 비밀 대화를 하기 전에 "한국말로 할지 영어로 할지", "암호 책은 뭘 쓸지", "당신이 진짜 그 사람 맞는지 신분증 좀 봅시다" 하고 서로 합의하는 단계입니다.
TLS 1.2 vs 1.3, 짧게
지금 실무에서 만나는 건 거의 TLS 1.2와 1.3입니다. 1.0/1.1은 보안 취약점 때문에 대부분의 브라우저에서 차단됐어요.
| 항목 | TLS 1.2 | TLS 1.3 |
|---|---|---|
| 핸드셰이크 RTT | 2회 | 1회 (0-RTT까지 가능) |
| 키 교환 | RSA, DHE, ECDHE 등 다양 | Forward Secrecy 필수 (ECDHE 등) |
| Cipher Suite | 30개 이상 | 5개로 단순화 |
새로 설정하는 서버라면 무조건 1.3을 우선으로 두는 게 좋습니다.
실전 예제
1) 핸드셰이크 직접 들여다보기
openssl s_client로 핸드셰이크를 그대로 볼 수 있습니다. 가장 손쉬운 방법이에요.
openssl s_client -connect www.google.com:443 -servername www.google.com
출력에서 눈여겨볼 부분은 다음과 같습니다.
SSL handshake has read 5234 bytes and written 422 bytes
---
New, TLSv1.3, Cipher is TLS_AES_256_GCM_SHA384
Server public key is 256 bit
Verification: OK
---TLSv1.3: 협상된 프로토콜 버전.Cipher is TLS_AES_256_GCM_SHA384: 합의된 암호 스위트.Verification: OK: 서버 인증서 검증 결과.
2) TLS 1.3 핸드셰이크 순서
간단히 풀면 이렇습니다.
- ClientHello: 클라이언트가 "내가 지원하는 TLS 버전, 암호 스위트 후보, 키 교환용 공개값(ECDHE share)"을 한 번에 전송.
- ServerHello: 서버가 "이 버전, 이 스위트로 가자, 내 키 교환용 공개값은 이거" 응답. 동시에 인증서와 핸드셰이크 종료 신호도 같이 보냄.
- 클라이언트가 인증서를 검증하고, 양쪽이 공유 비밀(shared secret)로 세션 키 생성.
- 끝. 이제부터 암호화된 통신 시작.
TLS 1.2는 여기에 키 교환 메시지가 한 번 더 왕복해서 총 2-RTT가 걸렸는데, 1.3은 1-RTT로 줄였습니다. 모바일·국제망처럼 RTT가 큰 환경에서는 체감 차이가 꽤 납니다.
3) 인증서 정보 까보기
서버 인증서 자체가 궁금하면 다음 명령어를 씁니다.
echo | openssl s_client -connect www.google.com:443 -servername www.google.com 2>/dev/null \
| openssl x509 -noout -subject -issuer -dates
만료일, 발급자(CA), 도메인을 한 번에 확인할 수 있어요. 모니터링 스크립트에 넣어두면 인증서 만료 때문에 새벽에 깨는 일은 확실히 줄어듭니다.
4) curl로 협상 결과 보기
curl의 -v 옵션도 유용합니다.
curl -v https://www.google.com 2>&1 | grep -E "SSL|TLS|certificate"
협상된 버전, 사용된 cipher, 인증서 체인이 한 줄씩 정리돼서 나옵니다.
자주 하는 실수
실무에서 TLS 관련해 자주 터지는 케이스를 정리해 봤습니다.
- 인증서 만료를 사람 눈으로만 관리: 1년짜리 인증서, "내년에 갱신해야지" 해두면 거의 100% 까먹습니다. 만료 30일 전 알람은 필수.
- 중간 인증서(체인) 누락: 서버에 leaf 인증서만 올리고 중간 인증서를 빼먹으면, 일부 브라우저나 모바일에서만 에러가 납니다. 데스크톱 크롬에서는 잘 되는데 안드로이드 앱에서만 실패하면 이걸 먼저 의심해 보세요.
openssl s_client출력의Certificate chain항목으로 확인 가능합니다. - SNI(Server Name Indication) 누락: 한 IP에 여러 도메인이 붙어 있을 때 SNI를 보내야 올바른 인증서가 옵니다.
openssl s_client에-servername을 안 붙이면 엉뚱한 인증서가 오는 이유가 바로 이거예요. - TLS 1.0/1.1 방치: 아직도 운영 서버에 열려 있는 경우가 있는데, 컴플라이언스 점검에서 거의 무조건 걸립니다. nginx면
ssl_protocols TLSv1.2 TLSv1.3;로 명시. - 약한 cipher 허용: RC4, 3DES, NULL, EXPORT가 들어 있으면 점검 대상.
nmap --script ssl-enum-ciphers -p 443 도메인으로 한 번에 확인 가능합니다.
저는 예전에 안드로이드 앱만 SSL 핸드셰이크 에러가 나서 한참 헤맸는데, 알고 보니 중간 인증서 하나가 빠져 있었습니다. 이거 한 줄 알면 30초, 모르면 반나절짜리 이슈예요.
마무리
정리하면 세 줄입니다.
- HTTPS = HTTP + TLS. TLS는 암호화·인증·무결성을 동시에 책임진다.
- 핸드셰이크는 "버전·암호 스위트 합의 → 키 교환 → 세션 키 생성" 순서. TLS 1.3은 1-RTT.
- 운영에서는
openssl s_client,curl -v,nmap세 개만 익혀두면 인증서·cipher 문제 대부분을 잡을 수 있다.
'보안' 카테고리의 다른 글
| [보안] 인증서 검증의 전 과정 - 체인 추적부터 OCSP·CRL 폐기 확인까지 (0) | 2026.05.20 |
|---|---|
| [보안] TLS 키 교환의 비밀 - 대칭키와 비대칭키는 어떻게 협력할까 (0) | 2026.05.19 |
| [보안] JWT 보안 - 흔히 하는 실수 5가지 (0) | 2026.05.14 |
| [보안] SQL Injection - 진짜 위험한 이유와 막는 법 (2) | 2026.05.04 |
| [보안] Deepfake 공격, 이제는 누구나 피해자가 될 수 있다 (0) | 2025.12.15 |