HTTPS
HTTP Secure (HyperText Transfer Protocol Secure)
HTTPS는 TLS(Transport Layer Security)로 암호화된 HTTP 프로토콜입니다. 기본 포트 443을 사용하며, 클라이언트와 서버 간 통신의 기밀성, 무결성, 인증을 보장합니다. 현대 웹의 필수 보안 요소로, 모든 웹사이트에서 HTTPS 사용이 권장됩니다.
HTTP Secure (HyperText Transfer Protocol Secure)
HTTPS는 TLS(Transport Layer Security)로 암호화된 HTTP 프로토콜입니다. 기본 포트 443을 사용하며, 클라이언트와 서버 간 통신의 기밀성, 무결성, 인증을 보장합니다. 현대 웹의 필수 보안 요소로, 모든 웹사이트에서 HTTPS 사용이 권장됩니다.
HTTPS(HyperText Transfer Protocol Secure)는 HTTP에 TLS(Transport Layer Security) 암호화 계층을 추가한 프로토콜입니다. HTTP가 평문(plaintext)으로 데이터를 전송하는 반면, HTTPS는 모든 통신을 암호화하여 중간자 공격(MITM), 도청, 데이터 변조를 방지합니다. 주소창의 자물쇠 아이콘과 "https://" 접두사로 HTTPS 연결을 확인할 수 있습니다.
HTTPS의 역사는 1994년 Netscape가 SSL(Secure Sockets Layer)을 도입하면서 시작되었습니다. SSL은 보안 취약점으로 인해 TLS로 대체되었고, 현재 TLS 1.3이 최신 표준입니다. TLS 1.3은 핸드셰이크 과정을 단순화하고(1-RTT), 레거시 암호화 알고리즘을 제거하여 보안과 성능을 모두 향상시켰습니다. NIST는 2024년까지 TLS 1.3 지원을 권장했으며, PCI DSS는 TLS 1.0 사용을 금지합니다.
HTTPS는 세 가지 핵심 보안 목표를 달성합니다. 기밀성(Confidentiality)은 AES-GCM, ChaCha20-Poly1305 등의 대칭키 암호화로 데이터를 보호합니다. 무결성(Integrity)은 메시지 인증 코드(MAC)로 데이터 변조를 감지합니다. 인증(Authentication)은 X.509 인증서로 서버 신원을 검증합니다. TLS 핸드셰이크 과정에서 ECDHE(Elliptic Curve Diffie-Hellman Ephemeral)로 키를 교환하고, 이후 통신은 대칭키로 암호화됩니다.
현대 웹에서 HTTPS는 선택이 아닌 필수입니다. Google은 HTTPS 사이트에 SEO 가점을 부여하고, Chrome은 HTTP 사이트에 "안전하지 않음" 경고를 표시합니다. Let's Encrypt 같은 무료 인증 기관(CA)의 등장으로 HTTPS 도입 비용 장벽이 사라졌습니다. HTTP/2와 HTTP/3는 HTTPS를 전제로 설계되어, 최신 웹 성능 최적화도 HTTPS에서만 가능합니다. 2025년 현재 전 세계 웹 트래픽의 95% 이상이 HTTPS를 사용합니다.
// Node.js HTTPS 서버 구현 예제
const https = require('https');
const fs = require('fs');
const express = require('express');
const app = express();
// TLS 인증서 로드 (Let's Encrypt 또는 자체 서명)
const options = {
key: fs.readFileSync('/etc/letsencrypt/live/example.com/privkey.pem'),
cert: fs.readFileSync('/etc/letsencrypt/live/example.com/fullchain.pem'),
// TLS 1.2 이상만 허용 (TLS 1.3 권장)
minVersion: 'TLSv1.2',
// 안전한 암호 스위트만 사용
ciphers: [
'TLS_AES_256_GCM_SHA384',
'TLS_CHACHA20_POLY1305_SHA256',
'TLS_AES_128_GCM_SHA256',
'ECDHE-RSA-AES256-GCM-SHA384',
'ECDHE-RSA-AES128-GCM-SHA256'
].join(':'),
// ECDHE 키 교환 우선
ecdhCurve: 'X25519:P-256:P-384'
};
// HSTS (HTTP Strict Transport Security) 헤더 설정
app.use((req, res, next) => {
res.setHeader('Strict-Transport-Security',
'max-age=31536000; includeSubDomains; preload');
next();
});
// HTTP를 HTTPS로 리다이렉트
const http = require('http');
http.createServer((req, res) => {
res.writeHead(301, {
'Location': `https://${req.headers.host}${req.url}`
});
res.end();
}).listen(80);
app.get('/', (req, res) => {
res.json({
message: 'Secure HTTPS Connection',
protocol: req.protocol,
tlsVersion: req.socket.getProtocol?.() || 'TLS'
});
});
// HTTPS 서버 시작 (포트 443)
https.createServer(options, app).listen(443, () => {
console.log('HTTPS server running on port 443');
});
// 인증서 자동 갱신 (Let's Encrypt certbot 사용)
// crontab: 0 0 1 * * certbot renew --post-hook "systemctl restart nodejs"
# Nginx TLS 1.3 최적 설정 (2025 베스트 프랙티스)
# /etc/nginx/conf.d/ssl.conf
server {
listen 80;
server_name example.com www.example.com;
# HTTP를 HTTPS로 301 리다이렉트
return 301 https://$host$request_uri;
}
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name example.com www.example.com;
# 인증서 경로 (Let's Encrypt)
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
# TLS 버전 - TLS 1.2, 1.3만 허용 (TLS 1.0, 1.1 비활성화)
ssl_protocols TLSv1.2 TLSv1.3;
# 암호 스위트 - TLS 1.3은 자동, TLS 1.2는 명시
ssl_ciphers 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에서는 클라이언트 우선
# 세션 캐싱 (성능 향상)
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 1d;
ssl_session_tickets off; # Perfect Forward Secrecy 보장
# OCSP Stapling (인증서 검증 속도 향상)
ssl_stapling on;
ssl_stapling_verify on;
ssl_trusted_certificate /etc/letsencrypt/live/example.com/chain.pem;
resolver 8.8.8.8 8.8.4.4 valid=300s;
resolver_timeout 5s;
# 보안 헤더
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
# DH 파라미터 (2048비트 이상)
ssl_dhparam /etc/nginx/ssl/dhparam.pem;
# 키 크기: RSA 2048비트 또는 ECDSA P-256 이상
# ECDSA 권장 (더 작은 키로 동등한 보안, 더 빠른 핸드셰이크)
location / {
root /var/www/html;
index index.html;
}
}
# DH 파라미터 생성: openssl dhparam -out /etc/nginx/ssl/dhparam.pem 2048
# 테스트: https://www.ssllabs.com/ssltest/
# Python에서 HTTPS 요청 및 인증서 검증
import requests
import ssl
import urllib3
from requests.adapters import HTTPAdapter
from urllib3.util.ssl_ import create_urllib3_context
# 기본 HTTPS 요청 (인증서 자동 검증)
response = requests.get('https://api.example.com/data')
print(f"Status: {response.status_code}")
print(f"TLS Version: {response.raw.version}")
# 커스텀 TLS 설정 어댑터
class TLS13Adapter(HTTPAdapter):
"""TLS 1.3 전용 어댑터"""
def init_poolmanager(self, *args, **kwargs):
ctx = create_urllib3_context()
# TLS 1.3만 허용 (최고 보안)
ctx.minimum_version = ssl.TLSVersion.TLSv1_3
# 안전한 암호 스위트
ctx.set_ciphers('TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256')
kwargs['ssl_context'] = ctx
return super().init_poolmanager(*args, **kwargs)
# TLS 1.3 전용 세션 사용
session = requests.Session()
session.mount('https://', TLS13Adapter())
try:
response = session.get('https://api.example.com/secure')
print("TLS 1.3 connection successful")
except requests.exceptions.SSLError as e:
print(f"TLS Error: {e}")
# 인증서 핀닝 (Certificate Pinning)
# 특정 인증서의 공개키 해시를 검증
EXPECTED_CERT_HASH = "sha256/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA="
def verify_certificate_pin(host, port=443):
"""인증서 핀 검증"""
import hashlib
import socket
context = ssl.create_default_context()
with socket.create_connection((host, port)) as sock:
with context.wrap_socket(sock, server_hostname=host) as ssock:
cert_der = ssock.getpeercert(binary_form=True)
cert_hash = hashlib.sha256(cert_der).digest()
cert_pin = f"sha256/{cert_hash.hex()}"
if cert_pin != EXPECTED_CERT_HASH:
raise ssl.SSLError(f"Certificate pin mismatch: {cert_pin}")
print(f"Certificate verified: {cert_pin}")
return True
# 주의: 자체 서명 인증서 허용은 보안 위험!
# requests.get(url, verify=False) # 절대 프로덕션에서 사용 금지!
"모든 서비스에 TLS 1.3을 적용했고, TLS 1.0/1.1은 비활성화했습니다. HSTS preload 리스트에도 등록되어 있어 브라우저가 항상 HTTPS로 접속합니다. SSL Labs 테스트에서 A+ 등급을 받았고, 인증서는 Let's Encrypt로 자동 갱신됩니다."
"로드밸런서에서 TLS 터미네이션을 하고, 백엔드는 내부망이라 HTTP로 통신합니다. 단, 민감한 데이터가 있으면 end-to-end 암호화가 필요해서 백엔드까지 mTLS(mutual TLS)를 적용해야 합니다. 인증서는 HashiCorp Vault로 중앙 관리하고 있습니다."
"verify=False로 인증서 검증을 끄면 안 됩니다. 개발 환경이라도 자체 서명 인증서를 발급해서 사용하세요. 프로덕션에서 이 코드가 배포되면 중간자 공격에 완전히 노출됩니다. 환경 변수로 분기하더라도 위험합니다."
HTTPS 페이지에서 HTTP 리소스(이미지, 스크립트)를 로드하면 보안 경고가 발생하고 차단될 수 있습니다. 모든 리소스를 HTTPS로 로드하거나, 프로토콜 상대 URL(//)을 사용하세요.
인증서가 만료되면 브라우저가 접속을 차단합니다. Let's Encrypt + certbot 자동 갱신을 설정하고, 만료 7일 전 알림을 받도록 모니터링하세요. 인증서 체인이 불완전하면 모바일에서 오류가 발생합니다.
TLS 1.0/1.1은 POODLE, BEAST 등 알려진 취약점이 있습니다. PCI DSS 규정에서도 TLS 1.0 사용을 금지합니다. 최소 TLS 1.2, 가능하면 TLS 1.3만 허용하세요.
TLS 1.3 기본 사용, HSTS 헤더 적용 (preload 포함), OCSP Stapling 활성화, RSA 2048비트 또는 ECDSA P-256 이상 키 사용, 인증서 자동 갱신, HTTP→HTTPS 301 리다이렉트를 구현하세요. SSL Labs에서 A+ 등급을 목표로 하세요.