HTTP와 HTTPS의 차이, TLS 동작 원리는 백엔드 개발자와 네트워크 입문자가 반드시 짚고 넘어야 할 핵심 CS 주제입니다. 브라우저 주소창에 자물쇠 아이콘이 뜨고 사라지는 것이 단순한 UI 장식처럼 보여도, 그 뒤에서는 암호화 협상·인증서 검증·대칭키 교환이라는 정교한 메커니즘이 작동하고 있습니다. 이 개념을 제대로 이해하지 못하면 보안 취약점을 코드 레벨에서 인지하지 못한 채 서비스를 배포하게 됩니다. 이 글에서는 HTTP의 구조적 한계부터 TLS 핸드셰이크 전 과정, X.509 인증서 구조, TLS 1.3의 개선점까지 단계별로 완전히 정리합니다.
목차
- HTTP의 구조와 한계 – 왜 평문 전송이 위험한가
- HTTPS의 핵심 – TLS가 제공하는 세 가지 보안 속성
- TLS 핸드셰이크 전 과정 – 연결이 만들어지는 순서
- X.509 인증서 구조와 인증 기관(CA) 체인
- TLS 1.2 vs TLS 1.3 – 무엇이 달라졌는가
- 실전 적용 – HTTPS 설정과 보안 강화 체크리스트
1. HTTP의 구조와 한계 – 왜 평문 전송이 위험한가
HTTP의 기본 동작 구조
**HTTP(HyperText Transfer Protocol)**는 클라이언트(브라우저)와 서버가 텍스트 기반 메시지를 주고받는 프로토콜입니다. 요청(Request)과 응답(Response)으로 구성되며, 기본 포트는 80번을 사용합니다. 1991년 처음 설계될 당시에는 인터넷이 소수 연구자들의 학술 네트워크였기 때문에 보안보다 단순성이 우선시됐습니다.
HTTP 메시지는 사람이 읽을 수 있는 평문(Plain Text)으로 전송됩니다. 아래는 실제 HTTP 요청 패킷의 구조입니다.
GET /login HTTP/1.1
Host: example.com
Content-Type: application/x-www-form-urlencoded
username=admin&password=mysecretpassword123
이 메시지가 네트워크를 통해 전달되는 동안 아무런 암호화 없이 그대로 이동합니다. 같은 Wi-Fi에 접속한 공격자가 패킷을 캡처(스니핑)하면 위의 내용을 그대로 읽을 수 있습니다.
HTTP의 세 가지 구조적 취약점
① 도청(Eavesdropping) – 기밀성 부재
HTTP 패킷은 경로상의 모든 노드(공유기, ISP, 프록시)에서 평문으로 읽힙니다. 공공 Wi-Fi 환경에서는 Wireshark 같은 도구로 수초 안에 타인의 로그인 정보, 쿠키, 세션 토큰을 가로챌 수 있습니다.
② 변조(Tampering) – 무결성 부재
HTTP는 전송 중 데이터가 변조되어도 감지할 방법이 없습니다. 실제로 일부 ISP나 악성 공유기는 HTTP 응답에 광고 스크립트를 주입하거나, 소프트웨어 다운로드 패킷을 악성코드로 교체하는 공격(SSL Stripping)을 수행한 사례가 있습니다.
③ 위조(Spoofing) – 인증 부재
클라이언트는 자신이 접속한 서버가 진짜인지 확인할 방법이 없습니다. 공격자가 진짜 서버와 동일하게 생긴 가짜 서버를 만들어 사용자를 유도하는 **피싱(Phishing)**이 가능한 이유가 바로 HTTP의 인증 부재 때문입니다.
[중간자 공격(MITM) 시나리오]
사용자 ──→ (공격자 노드) ──→ 진짜 서버
↑
패킷 도청 + 변조 + 위조 가능
→ 사용자는 진짜 서버와 통신한다고 착각
2. HTTPS의 핵심 – TLS가 제공하는 세 가지 보안 속성
TLS란 무엇인가
**HTTPS(HTTP Secure)**는 HTTP에 TLS(Transport Layer Security) 레이어를 추가한 프로토콜로, 기본 포트는 443번입니다. 흔히 SSL이라고 부르기도 하지만 SSL(Secure Sockets Layer)은 구버전(SSL 2.0, 3.0) 명칭이고, 현재는 TLS 1.2·1.3이 표준입니다. TLS는 HTTP 아래에 위치하여 데이터를 암호화한 뒤 TCP로 전달합니다.
[프로토콜 스택]
HTTP (응용 계층)
↓
TLS (보안 계층) ← 암호화·인증·무결성 담당
↓
TCP (전송 계층)
↓
IP (네트워크 계층)
TLS가 제공하는 세 가지 보안 속성
① 기밀성(Confidentiality) – 도청 방지
TLS는 대칭키 암호화(AES 등)로 전송 데이터를 암호화합니다. 공격자가 패킷을 캡처해도 암호화된 바이너리만 보이므로 내용을 읽을 수 없습니다. 같은 평문이라도 매번 다른 암호문이 생성되도록 **초기화 벡터(IV)**를 사용합니다.
② 무결성(Integrity) – 변조 방지
TLS는 각 메시지에 MAC(Message Authentication Code) 또는 AEAD(Authenticated Encryption with Associated Data) 태그를 붙입니다. 수신 측에서 이 태그를 검증하므로, 전송 중 단 1비트라도 변조되면 즉시 감지하고 연결을 끊습니다.
③ 인증(Authentication) – 위조 방지
TLS는 서버가 X.509 디지털 인증서를 제시하고, 클라이언트가 신뢰할 수 있는 **인증 기관(CA)**이 서명했는지 검증합니다. 이를 통해 “내가 접속한 서버가 진짜 example.com인가”를 수학적으로 증명합니다.
| 보안 속성 | HTTP | HTTPS(TLS) | 사용 기술 |
|---|---|---|---|
| 기밀성 | ❌ | ✅ | AES-GCM, ChaCha20 |
| 무결성 | ❌ | ✅ | HMAC-SHA256, AEAD |
| 인증 | ❌ | ✅ | X.509 인증서, PKI |
3. TLS 핸드셰이크 전 과정 – 연결이 만들어지는 순서
TLS 1.2 핸드셰이크 (4-RTT → 2-RTT)
TLS 핸드셰이크는 클라이언트와 서버가 암호화 통신을 시작하기 전에 암호화 방식 협상, 인증서 교환, 세션 키 생성을 수행하는 과정입니다. 악수(Handshake)처럼 서로의 신원을 확인하고 통신 규칙을 맞추는 단계입니다.
클라이언트 서버
│ │
│──── ① ClientHello ────────────────→│
│ (지원 TLS 버전, 암호 수트 목록, │
│ 랜덤값 Client Random) │
│ │
│←─── ② ServerHello ────────────────│
│ (선택된 암호 수트, │
│ 랜덤값 Server Random) │
│ │
│←─── ③ Certificate ────────────────│
│ (서버의 X.509 인증서) │
│ │
│←─── ④ ServerHelloDone ────────────│
│ │
│ [클라이언트: 인증서 검증] │
│ [Pre-Master Secret 생성] │
│ │
│──── ⑤ ClientKeyExchange ─────────→│
│ (서버 공개키로 암호화한 │
│ Pre-Master Secret) │
│ │
│ [양측: Master Secret → 세션키 파생]
│ │
│──── ⑥ ChangeCipherSpec ──────────→│
│──── ⑦ Finished ──────────────────→│
│←─── ⑧ ChangeCipherSpec ───────────│
│←─── ⑨ Finished ───────────────────│
│ │
│══════ 암호화된 HTTP 통신 시작 ══════│
각 단계를 자세히 살펴보겠습니다.
① ClientHello 클라이언트가 지원하는 TLS 버전 목록, 암호 수트(Cipher Suite) 목록, 랜덤값(Client Random, 32바이트)을 서버에 전송합니다. 암호 수트는 “어떤 키 교환 알고리즘 + 어떤 대칭 암호 + 어떤 해시 함수를 사용할 것인가”를 나타냅니다. 예: TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
② ServerHello 서버가 클라이언트가 제안한 목록 중 자신이 지원하는 가장 강력한 암호 수트 하나를 선택하고, 랜덤값(Server Random)과 함께 응답합니다.
③ Certificate 서버가 자신의 X.509 인증서를 클라이언트에게 전송합니다. 인증서에는 서버의 도메인명, 공개키, 유효 기간, CA 서명이 포함되어 있습니다.
⑤ ClientKeyExchange & 세션 키 파생 클라이언트가 Pre-Master Secret을 생성하고, 서버 인증서에서 추출한 공개키로 암호화하여 전송합니다. 서버는 자신의 개인키로 복호화해 Pre-Master Secret을 얻습니다. 양측은 Client Random + Server Random + Pre-Master Secret을 조합해 동일한 Master Secret을 도출하고, 여기서 실제 암호화에 쓸 세션 대칭키를 파생합니다.
[세션 키 파생 공식]
Master Secret = PRF(Pre-Master Secret, Client Random + Server Random)
세션 키 = 키 파생 함수(Master Secret, 추가 파라미터)
공개키로 암호화 → 서버만 개인키로 복호화 가능
→ Pre-Master Secret이 안전하게 전달됨
→ 이후 통신은 빠른 대칭키 암호화로 처리
4. X.509 인증서 구조와 인증 기관(CA) 체인
X.509 인증서의 내부 구조
X.509 인증서는 서버의 신원을 증명하는 디지털 신분증입니다. 여권처럼 발급 기관의 서명이 담겨 있으며, 구조는 다음과 같습니다.
┌─────────────────────────────────────────┐
│ X.509 인증서 구조 │
├─────────────────────────────────────────┤
│ Version : v3 │
│ Serial Number : 3A:B1:C2:... (고유 번호)│
│ Signature Algo : SHA256withRSA │
├─────────────────────────────────────────┤
│ Subject (소유자 정보) │
│ CN = www.example.com ← 도메인명 │
│ O = Example Corp ← 조직명 │
│ C = KR ← 국가 코드 │
├─────────────────────────────────────────┤
│ Issuer (발급자 정보) │
│ CN = DigiCert TLS RSA SHA256 2020 │
│ O = DigiCert Inc │
├─────────────────────────────────────────┤
│ Validity (유효 기간) │
│ Not Before : 2024-01-01 │
│ Not After : 2025-01-01 │
├─────────────────────────────────────────┤
│ Public Key : RSA 2048-bit 공개키 │
├─────────────────────────────────────────┤
│ Extensions (확장 필드) │
│ SAN : DNS:www.example.com │
│ DNS:example.com (다중 도메인) │
│ Key Usage : digitalSignature │
│ Basic Constraints : CA:FALSE │
├─────────────────────────────────────────┤
│ Signature : CA의 개인키로 서명한 값 │
│ (위 모든 필드의 해시를 CA가 서명) │
└─────────────────────────────────────────┘
인증서 체인(Chain of Trust) 검증 원리
브라우저가 서버 인증서를 신뢰하는 과정은 **신뢰 체인(Chain of Trust)**을 따라 루트 CA까지 거슬러 올라가는 검증입니다. 마치 “이 서류는 A가 보증했고, A는 B가 보증했으며, B는 국가가 보증한다”는 방식입니다.
[인증서 체인 검증 흐름]
① 서버 인증서 (End-Entity Certificate)
Subject: www.example.com
Issuer : DigiCert TLS RSA G4
서명검증 ──→ DigiCert TLS RSA G4의 공개키로 검증
② 중간 CA 인증서 (Intermediate CA)
Subject: DigiCert TLS RSA G4
Issuer : DigiCert Global Root CA
서명검증 ──→ DigiCert Global Root CA의 공개키로 검증
③ 루트 CA 인증서 (Root CA)
Subject: DigiCert Global Root CA
Issuer : DigiCert Global Root CA ← 자기 자신 서명(Self-Signed)
✅ 브라우저의 신뢰 저장소(Trust Store)에 사전 내장
→ 체인이 끊어지지 않으면 인증서 신뢰 ✅
→ 어느 단계에서라도 서명 불일치 → "인증서 오류" 경고 ❌
인증서 종류 비교
| 종류 | 검증 수준 | 발급 기간 | 표시 방식 | 적합 대상 |
|---|---|---|---|---|
| DV (Domain Validation) | 도메인 소유 확인 | 수 분~수 시간 | 자물쇠 아이콘 | 개인 블로그, 스타트업 |
| OV (Organization Validation) | 기업 실체 확인 | 수 일 | 자물쇠 + 조직명 | 일반 기업 사이트 |
| EV (Extended Validation) | 엄격한 기업 심사 | 수 주 | 초록 주소창(구형) | 금융, 전자상거래 |
| Wildcard | 서브도메인 전체 | DV/OV 동일 | 자물쇠 | *.example.com |
| SAN | 복수 도메인 | DV/OV 동일 | 자물쇠 | 다중 도메인 운영 |
OpenSSL로 인증서 내용 직접 확인
bash
# 실제 서버의 인증서 내용 출력
openssl s_client -connect www.example.com:443 -showcerts </dev/null 2>/dev/null \
| openssl x509 -noout -text
# 로컬 인증서 파일 내용 확인
openssl x509 -in certificate.crt -noout -text
# 인증서 유효 기간만 빠르게 확인
openssl x509 -in certificate.crt -noout -dates
# 출력:
# notBefore=Jan 1 00:00:00 2024 GMT
# notAfter=Jan 1 00:00:00 2025 GMT
# 인증서 체인 전체 검증
openssl verify -CAfile ca-bundle.crt certificate.crt
5. TLS 1.2 vs TLS 1.3 – 무엇이 달라졌는가
TLS 1.3의 핵심 개선 사항
2018년 RFC 8446으로 표준화된 TLS 1.3은 TLS 1.2 대비 보안성과 성능을 동시에 향상시켰습니다. 현재 대부분의 최신 브라우저와 서버는 TLS 1.3을 기본으로 사용합니다.
① RTT 감소 – 핸드셰이크 1-RTT로 단축
TLS 1.2는 완전한 핸드셰이크에 2-RTT(왕복 2회)가 필요했습니다. TLS 1.3은 1-RTT로 단축되었고, 이전에 연결한 적 있는 서버라면 **0-RTT(Early Data)**로 첫 메시지부터 암호화 데이터를 전송할 수 있습니다.
[TLS 1.2] 2-RTT
클라이언트 서버
│──ClientHello──→│ RTT 1 (↑)
│←─ServerHello──│
│←─Certificate──│
│←─ServerDone───│ RTT 1 (↓)
│──ClientKeyEx─→│ RTT 2 (↑)
│──ChangeCipher→│
│──Finished────→│
│←─ChangeCipher─│ RTT 2 (↓)
│←─Finished─────│
│══HTTP 시작══════│
[TLS 1.3] 1-RTT
클라이언트 서버
│──ClientHello──→│ RTT 1 (↑)
│ (키 교환 포함) │
│←─ServerHello──│
│←─Certificate──│
│←─Finished─────│ RTT 1 (↓)
│──Finished────→│
│══HTTP 시작══════│ ← 1 RTT 절감
② 취약한 암호 알고리즘 제거
TLS 1.3은 알려진 취약점이 있는 암호 알고리즘을 모두 제거했습니다.
| 구분 | TLS 1.2 | TLS 1.3 |
|---|---|---|
| 키 교환 | RSA, DH, ECDHE | ECDHE, DHE만 허용 |
| 대칭 암호 | AES-CBC, RC4, 3DES 등 | AES-GCM, ChaCha20만 허용 |
| 해시 함수 | MD5, SHA-1 포함 | SHA-256, SHA-384만 허용 |
| 압축 | 지원 (CRIME 취약점) | 완전 제거 |
③ 순방향 비밀성(Forward Secrecy) 강제
TLS 1.2에서는 RSA 키 교환을 사용하면, 나중에 서버 개인키가 유출됐을 때 과거에 캡처한 트래픽을 전부 복호화할 수 있었습니다. TLS 1.3은 ECDHE/DHE 키 교환만 허용하여 세션마다 임시 키를 생성합니다. 개인키가 유출돼도 과거 세션을 복호화할 수 없습니다.
[Forward Secrecy 원리]
세션 1: 임시 키 K1 생성 → 암호화 → K1 폐기
세션 2: 임시 키 K2 생성 → 암호화 → K2 폐기
세션 3: 임시 키 K3 생성 → 암호화 → K3 폐기
→ 서버 개인키 유출돼도 K1, K2, K3는 이미 삭제됨
→ 과거 세션 복호화 불가 ✅
6. 실전 적용 – HTTPS 설정과 보안 강화 체크리스트
Let’s Encrypt로 무료 인증서 발급
Let’s Encrypt는 비영리 CA로, 90일 유효기간의 DV 인증서를 무료로 발급합니다. Certbot을 이용하면 자동 발급과 갱신이 가능합니다.
bash
# Certbot 설치 (Ubuntu/Debian 기준)
sudo apt update
sudo apt install certbot python3-certbot-nginx
# Nginx 플러그인으로 인증서 자동 발급 + Nginx 설정 자동 수정
sudo certbot --nginx -d example.com -d www.example.com
# 발급 후 인증서 위치
# /etc/letsencrypt/live/example.com/fullchain.pem ← 서버 인증서 + 중간 CA
# /etc/letsencrypt/live/example.com/privkey.pem ← 서버 개인키
# 자동 갱신 테스트 (실제 갱신 없이 시뮬레이션)
sudo certbot renew --dry-run
# crontab으로 자동 갱신 등록 (매일 2회 실행, 만료 30일 전 자동 갱신)
echo "0 0,12 * * * root certbot renew --quiet" | sudo tee /etc/cron.d/certbot
Nginx TLS 1.3 강화 설정
nginx
# /etc/nginx/sites-available/example.com
server {
listen 443 ssl http2;
server_name example.com www.example.com;
# 인증서 경로
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
# TLS 1.2 이상만 허용 (TLS 1.0, 1.1 비활성화)
ssl_protocols TLSv1.2 TLSv1.3;
# 강력한 암호 수트만 허용
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305;
ssl_prefer_server_ciphers off; # TLS 1.3에서는 off 권장
# HSTS: 브라우저에게 "이 도메인은 항상 HTTPS만 사용" 지시
# max-age=31536000 = 1년, includeSubDomains = 서브도메인 포함
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
# 기타 보안 헤더
add_header X-Frame-Options DENY;
add_header X-Content-Type-Options nosniff;
add_header Referrer-Policy strict-origin-when-cross-origin;
# OCSP Stapling: 인증서 폐기 여부를 서버가 미리 확인해 전달
ssl_stapling on;
ssl_stapling_verify on;
resolver 8.8.8.8 1.1.1.1 valid=300s;
location / {
proxy_pass http://localhost:8080;
}
}
# HTTP → HTTPS 리다이렉트
server {
listen 80;
server_name example.com www.example.com;
return 301 https://$host$request_uri;
}
Spring Boot HTTPS 설정
yaml
# application.yml (Spring Boot)
server:
port: 8443
ssl:
# JKS 또는 PKCS12 키스토어 경로
key-store: classpath:keystore.p12
key-store-password: changeit
key-store-type: PKCS12
key-alias: myserver
# TLS 1.2 이상만 허용
enabled-protocols:
- TLSv1.2
- TLSv1.3
java
// HTTP → HTTPS 리다이렉트 강제 설정 (Spring Boot)
@Configuration
public class HttpsRedirectConfig {
/**
* HTTP(8080) 요청을 HTTPS(8443)로 자동 리다이렉트
*/
@Bean
public ServletWebServerFactory servletContainer() {
TomcatServletWebServerFactory tomcat =
new TomcatServletWebServerFactory() {
@Override
protected void postProcessContext(Context context) {
SecurityConstraint securityConstraint = new SecurityConstraint();
securityConstraint.setUserConstraint("CONFIDENTIAL");
SecurityCollection collection = new SecurityCollection();
collection.addPattern("/*");
securityConstraint.addCollection(collection);
context.addConstraint(securityConstraint);
}
};
tomcat.addAdditionalTomcatConnectors(redirectConnector());
return tomcat;
}
private Connector redirectConnector() {
Connector connector = new Connector("org.apache.coyote.http11.Http11NioProtocol");
connector.setScheme("http");
connector.setPort(8080);
connector.setSecure(false);
connector.setRedirectPort(8443); // HTTPS 포트로 리다이렉트
return connector;
}
}
HTTPS 보안 강화 최종 체크리스트
✅ 기본 보안
□ TLS 1.2 이상 사용 (TLS 1.0, 1.1 비활성화)
□ TLS 1.3 우선 지원
□ 취약한 암호 수트 제거 (RC4, 3DES, MD5, SHA-1 기반 제외)
□ HTTP → HTTPS 301 리다이렉트 설정
✅ 인증서 관리
□ 인증서 유효 기간 30일 이전 갱신 알림 설정
□ 중간 CA 인증서(체인) 포함하여 배포
□ OCSP Stapling 활성화
□ 인증서 SANs에 www·비www 도메인 모두 포함
✅ 헤더 보안
□ HSTS 헤더 설정 (max-age ≥ 31536000)
□ HSTS preload 목록 등록 검토 (hstspreload.org)
□ X-Content-Type-Options: nosniff
□ X-Frame-Options: DENY 또는 SAMEORIGIN
✅ 모니터링
□ SSL Labs (ssllabs.com/ssltest) A+ 등급 달성
□ 인증서 만료 모니터링 자동화
□ Mixed Content(HTTP 리소스 혼재) 제거
결론
HTTP와 HTTPS의 차이, TLS 동작 원리를 이해하면 웹 보안의 절반을 파악한 것과 다름없습니다. HTTP는 기밀성·무결성·인증 모두 결여된 평문 프로토콜이며, HTTPS는 TLS를 통해 이 세 가지를 수학적으로 보장합니다. TLS 핸드셰이크는 공개키 암호화로 세션 키를 안전하게 교환하고, 이후 빠른 대칭키 암호화로 전환하는 구조이며, TLS 1.3은 이 과정을 1-RTT로 단축하고 취약 알고리즘을 모두 제거했습니다. 오늘 배운 인증서 체인 검증 원리와 Nginx 보안 설정을 바탕으로, 지금 운영 중인 서비스의 SSL Labs 점수를 직접 측정해 보세요.
⚠️ 면책 고지: 본 글은 기술 학습 및 참고 목적으로 작성된 가이드입니다. 제시된 Nginx·Spring Boot 설정 예시는 일반적인 권장 사항이며, 실제 운영 환경에 따라 세부 설정이 달라질 수 있습니다. 보안 설정 적용 전 반드시 전문가 검토와 충분한 테스트를 거치시기 바랍니다. 설정 변경으로 인한 서비스 장애나 보안 사고에 대해 본 블로그는 책임을 지지 않습니다.
답글 남기기