
HTTP는 웹에서 클라이언트와 서버 간의 데이터 전송을 위한 프로토콜입니다. 하지만 HTTP는 다음과 같은 보안 취약점을 가지고 있습니다:
HTTP의 한계:
클라이언트 ←→ [평문 데이터] ←→ 서버
↑
해커가 도청 가능
HTTPS는 HTTP에 SSL/TLS 암호화 계층을 추가한 프로토콜입니다. 기본 포트는 443번을 사용합니다.
HTTPS의 장점:
클라이언트 ←→ [암호화된 데이터] ←→ 서버
↑
해커가 해독 불가능
| 구분 | HTTP | HTTPS |
|---|---|---|
| 보안 | 없음 | SSL/TLS 암호화 |
| 포트 | 80 | 443 |
| 속도 | 빠름 | 상대적으로 느림 (암호화 오버헤드) |
| 인증서 | 불필요 | SSL/TLS 인증서 필요 |
| SEO | 불리함 | 유리함 (Google 순위 요소) |
| 브라우저 표시 | "안전하지 않음" | 자물쇠 아이콘 |
SSL은 1994년 넷스케이프에서 개발한 암호화 프로토콜입니다. 현재는 보안 취약점으로 인해 사용되지 않습니다.
SSL 버전 히스토리:
TLS는 SSL의 후속 표준으로, IETF에서 관리하는 공개 표준입니다.
TLS 버전별 특징:
// Node.js에서 TLS 버전 확인
const https = require('https');
const options = {
hostname: 'example.com',
port: 443,
path: '/',
method: 'GET',
secureProtocol: 'TLSv1_3_method' // TLS 1.3 강제
};
TLS 핸드셰이크는 클라이언트와 서버가 안전한 연결을 설정하기 위한 과정입니다. 이 과정에서 암호화 방식을 협상하고, 서버 인증을 수행하며, 세션 키를 교환합니다.
sequenceDiagram
participant C as 클라이언트
participant S as 서버
Note over C,S: 1. 초기 협상
C->>S: ClientHello (지원 TLS 버전, 암호화 스위트, 랜덤값)
S->>C: ServerHello (선택된 TLS 버전, 암호화 스위트, 랜덤값)
Note over C,S: 2. 서버 인증
S->>C: Certificate (서버 인증서)
S->>C: ServerKeyExchange (키 교환 정보)
S->>C: ServerHelloDone (서버 메시지 완료)
Note over C,S: 3. 클라이언트 키 교환
C->>S: ClientKeyExchange (사전 마스터 시크릿)
C->>S: ChangeCipherSpec (암호화 스위트 활성화)
C->>S: Finished (핸드셰이크 완료 확인)
Note over C,S: 4. 완료
S->>C: ChangeCipherSpec (서버 암호화 활성화)
S->>C: Finished (서버 핸드셰이크 완료)
Note over C,S: 5. 암호화된 통신 시작
C->>S: 암호화된 HTTP 요청
S->>C: 암호화된 HTTP 응답
클라이언트가 서버에게 연결을 요청하며 다음 정보를 전송합니다:
ClientHello {
version: TLS 1.2,
random: [32바이트 랜덤값],
cipher_suites: [
TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
...
],
extensions: [
server_name: "example.com",
supported_groups: [secp256r1, secp384r1],
...
]
}
서버가 클라이언트의 요청에 응답하며 선택한 설정을 전송합니다:
ServerHello {
version: TLS 1.2,
random: [32바이트 랜덤값],
cipher_suite: TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
extensions: [...]
}
서버가 자신의 SSL/TLS 인증서를 클라이언트에게 전송합니다. 클라이언트는 이 인증서를 검증하여 서버의 신원을 확인합니다.
선택된 키 교환 알고리즘(ECDHE, RSA 등)에 따라 암호화 키를 안전하게 교환합니다.
클라이언트와 서버의 랜덤값, 그리고 사전 마스터 시크릿을 조합하여 실제 암호화에 사용할 세션 키를 생성합니다.
TLS 1.3에서는 핸드셰이크 과정이 더욱 단순화되어 성능이 향상되었습니다:
sequenceDiagram
participant C as 클라이언트
participant S as 서버
C->>S: ClientHello + KeyShare
S->>C: ServerHello + KeyShare + Certificate + Finished
C->>S: Finished
Note over C,S: 즉시 암호화된 통신 가능 (1-RTT)
SSL/TLS 인증서는 X.509 표준을 따르며, 다음과 같은 정보를 포함합니다:
Certificate {
Version: 3
Serial Number: 12:34:56:78:9a:bc:de:f0
Signature Algorithm: sha256WithRSAEncryption
Issuer: CN=Let's Encrypt Authority X3, O=Let's Encrypt, C=US
Validity:
Not Before: Jan 1 00:00:00 2024 GMT
Not After : Apr 1 23:59:59 2024 GMT
Subject: CN=example.com
Subject Public Key Info:
Public Key Algorithm: rsaEncryption
RSA Public Key: (2048 bit)
X509v3 extensions:
X509v3 Subject Alternative Name:
DNS:example.com, DNS:www.example.com
X509v3 Key Usage:
Digital Signature, Key Encipherment
Signature: [디지털 서명]
}
# Let's Encrypt로 DV 인증서 발급
certbot certonly --webroot -w /var/www/html -d example.com
Root CA (자체 서명)
↓
Intermediate CA (Root CA가 서명)
↓
End Entity Certificate (Intermediate CA가 서명)
// Node.js에서 인증서 체인 확인
const https = require('https');
const { execSync } = require('child_process');
function checkCertChain(hostname) {
try {
const output = execSync(`openssl s_client -showcerts -connect ${hostname}:443 < /dev/null`);
console.log('Certificate chain verified');
} catch (error) {
console.error('Certificate validation failed:', error.message);
}
}
checkCertChain('example.com');
# Certbot을 이용한 Let's Encrypt 인증서 자동 갱신
# crontab 설정
0 12 * * * /usr/bin/certbot renew --quiet
// 인증서 만료일 확인 스크립트
const https = require('https');
function checkCertExpiry(hostname) {
const options = {
hostname: hostname,
port: 443,
method: 'GET',
rejectUnauthorized: false
};
const req = https.request(options, (res) => {
const cert = res.socket.getPeerCertificate();
const expiryDate = new Date(cert.valid_to);
const daysUntilExpiry = Math.ceil((expiryDate - new Date()) / (1000 * 60 * 60 * 24));
console.log(`${hostname} 인증서 만료까지: ${daysUntilExpiry}일`);
if (daysUntilExpiry < 30) {
console.warn('⚠️ 인증서 갱신이 필요합니다!');
}
});
req.on('error', (error) => {
console.error('Error:', error.message);
});
req.end();
}
checkCertExpiry('example.com');
server {
listen 443 ssl http2;
server_name example.com;
# 인증서 설정
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
# SSL 보안 설정
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256;
ssl_prefer_server_ciphers off;
# HSTS 설정
add_header Strict-Transport-Security "max-age=63072000" always;
# OCSP Stapling
ssl_stapling on;
ssl_stapling_verify on;
ssl_trusted_certificate /etc/letsencrypt/live/example.com/chain.pem;
# 성능 최적화
ssl_session_cache shared:le_nginx_SSL:10m;
ssl_session_timeout 1440m;
ssl_session_tickets off;
}
# 개인키 파일 권한 설정
chmod 600 /etc/ssl/private/example.com.key
chown root:root /etc/ssl/private/example.com.key
# 개인키 암호화
openssl rsa -in example.com.key -des3 -out example.com.encrypted.key
모든 공인 CA에서 발급하는 인증서는 CT 로그에 기록됩니다:
// CT 로그 조회 예시
async function checkCTLogs(domain) {
const response = await fetch(`https://crt.sh/?q=${domain}&output=json`);
const certificates = await response.json();
certificates.forEach(cert => {
console.log(`발급일: ${cert.not_before}, 만료일: ${cert.not_after}`);
console.log(`CA: ${cert.issuer_name}`);
});
}
// 인증서 핀 생성
const crypto = require('crypto');
function generateSPKIFingerprint(cert) {
const spki = cert.raw;
const hash = crypto.createHash('sha256');
hash.update(spki);
return hash.digest('base64');
}
# TLS 세션 캐시 설정
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
# OCSP 응답 캐싱으로 핸드셰이크 속도 향상
ssl_stapling on;
ssl_stapling_verify on;
resolver 8.8.8.8 8.8.4.4 valid=300s;
// Express.js 보안 헤더 설정
app.use((req, res, next) => {
// HSTS
res.setHeader('Strict-Transport-Security', 'max-age=31536000; includeSubDomains');
// CSP
res.setHeader('Content-Security-Policy', "default-src 'self'; script-src 'self' 'unsafe-inline'");
// X-Frame-Options
res.setHeader('X-Frame-Options', 'DENY');
// X-Content-Type-Options
res.setHeader('X-Content-Type-Options', 'nosniff');
next();
});
# SSL Labs 테스트 자동화
curl -s "https://api.ssllabs.com/api/v3/analyze?host=example.com" | jq '.grade'
# testssl.sh를 이용한 종합 테스트
./testssl.sh --fast --parallel example.com
HTTPS는 현대 웹의 필수 요소가 되었습니다. TLS/SSL의 동작 원리를 이해하고 적절한 인증서를 선택하여 구현하는 것은 웹 개발자의 기본 역량입니다.
보안은 한 번 설정하고 끝나는 것이 아니라 지속적인 관리와 업데이트가 필요한 영역입니다. 인증서 갱신, 보안 패치, 새로운 취약점 대응 등을 통해 안전한 웹 환경을 유지해야 합니다.
앞으로는 양자 컴퓨팅 시대를 대비한 새로운 암호화 기술과 더욱 강화된 프라이버시 보호 기술들이 등장할 예정입니다. 이러한 변화에 발맞춰 지속적인 학습과 대응이 필요할 것입니다.