인증서 실무
TLS와 인증서의 원리를 이해했으니, 이제 실무에서 인증서를 어떻게 발급하고 관리하는지 살펴보겠습니다.
Let's Encrypt와 자동 갱신
Let's Encrypt는 무료로 TLS 인증서를 발급하는 비영리 CA입니다. 2015년 출시 이후, 전 세계 HTTPS 보급률을 극적으로 높인 프로젝트입니다.
Certbot (클라이언트) Let's Encrypt (CA 서버)
│ │
│── 1. 인증서 발급 요청 ──────────────────→│
│ "example.com 인증서를 주세요" │
│ │
│←─ 2. Challenge 발급 ─────────────────────│
│ "이 도메인을 소유하고 있다면 │
│ 다음 중 하나를 증명하세요" │
│ - HTTP-01: /.well-known/acme-challenge/│
│ - DNS-01: TXT 레코드 추가 │
│ │
│── 3. Challenge 응답 ────────────────────→│
│ (HTTP: 특정 경로에 토큰 배치) │
│ (DNS: _acme-challenge.example.com TXT) │
│ │
│ [CA가 실제 도메인에 접근하여 검증] │
│ │
│←─ 4. 인증서 발급 ────────────────────────│
│ * 서버 인증서 (PEM) │
│ * 중간 CA 인증서 │
│ * 유효 기간: 90일 │
│ │
│ [certbot이 웹 서버에 인증서 설치] │
│ [cron/systemd로 60일마다 자동 갱신] │| 특징 | 설명 |
|---|---|
| 무료 | 비용 부담 없이 누구나 HTTPS 적용 |
| 자동화 | ACME 프로토콜로 발급/갱신 자동화 |
| 유효 기간 90일 | 키 유출 영향 최소화, 자동 갱신 강제 |
| DV 인증서만 | 도메인 소유 확인만 (기업 신원 확인 없음) |
| 와일드카드 | DNS-01로 *.example.com 발급 가능 |
# Certbot으로 Let's Encrypt 인증서 발급 (Nginx)
sudo certbot --nginx -d example.com -d www.example.com
# 인증서 갱신 테스트 (실제 갱신하지 않음)
sudo certbot renew --dry-run
# 자동 갱신 cron 설정 (60일마다)
echo "0 3 */60 * * certbot renew --quiet" | sudo crontab -
# 발급된 인증서 확인
sudo certbot certificates
# Certificate Name: example.com
# Domains: example.com www.example.com
# Expiry Date: 2024-04-15 (VALID: 89 days)
# DNS-01 Challenge로 와일드카드 인증서 발급
sudo certbot certonly --manual --preferred-challenges dns \
-d "*.example.com" -d "example.com"DV (Domain Validation)
검증 대상: 도메인 소유권만
발급 시간: 수 분
비용: 무료 ~ 저렴
용도: 개인 사이트, 소규모 서비스
→ Let's Encrypt가 이 유형
OV (Organization Validation)
검증 대상: 도메인 + 조직 실재 확인
발급 시간: 수 일
비용: 유료
용도: 기업 사이트
EV (Extended Validation)
검증 대상: 도메인 + 조직 + 법적 실재
발급 시간: 수 주
비용: 고가
용도: 금융, 전자상거래
(과거: 녹색 주소창, 현재: 브라우저가 구분 안 함)대부분의 클라우드 서비스(AWS ACM, Cloudflare, Vercel)는 인증서를 자동으로 발급하고 갱신해 주므로, 개발자가 직접 Certbot을 실행할 일은 점점 줄어들고 있습니다.
자체 서명 인증서
개발 환경에서 HTTPS를 테스트하려면 인증서가 필요한데, 매번 Let's Encrypt를 사용하기는 번거롭습니다. 이때 자체 서명 인증서(Self-Signed Certificate)를 만들어 사용합니다.
CA 서명 인증서
Root CA → 중간 CA → 서버 인증서
↑ CA가 서명
→ 브라우저가 Root CA를 신뢰 → 체인 검증 성공 → 자물쇠 ✓
자체 서명 인증서
서버 인증서
↑ 자기 자신이 서명
→ 브라우저 신뢰 저장소에 없음 → 검증 실패 → 경고! ✗# OpenSSL로 자체 서명 인증서 생성
openssl req -x509 -newkey rsa:2048 -nodes \
-keyout server.key -out server.crt -days 365 \
-subj "/CN=localhost"
# 생성된 인증서 내용 확인
openssl x509 -in server.crt -noout -text | head -20
# mkcert 사용 (로컬 CA 설치로 경고 없이 개발)
# 1. 로컬 CA 설치 (최초 1회)
mkcert -install
# 2. localhost용 인증서 생성
mkcert localhost 127.0.0.1 ::1
# → localhost+2.pem (인증서)
# → localhost+2-key.pem (개인키)
# 3. Node.js에서 사용
# const https = require('https');
# const options = {
# cert: fs.readFileSync('localhost+2.pem'),
# key: fs.readFileSync('localhost+2-key.pem')
# };자체 서명 인증서는 절대로 프로덕션 환경에서 사용해서는 안 됩니다.
SNI
SNI(Server Name Indication)는 TLS 핸드셰이크에서 클라이언트가 접속하려는 호스트명을 서버에 알려주는 확장입니다.
하나의 서버(IP: 93.184.216.34)에서 여러 도메인 호스팅
- example.com
- shop.example.com
- blog.example.com
SNI 없이 (과거)
Client → TLS 연결 → 서버: "어떤 인증서를 보내야 하지?"
→ IP당 하나의 인증서만 가능
→ 도메인마다 별도 IP 필요 (비용 증가)
SNI 있음 (현재)
Client Hello에 "shop.example.com" 포함
→ 서버: "shop.example.com 인증서를 보내야겠군"
→ 하나의 IP로 여러 도메인의 인증서 제공 가능
┌────────────────────────────────────────────┐
│ Client Hello │
│ ┌──────────────────────────────────────┐ │
│ │ TLS Version: 1.3 │ │
│ │ Cipher Suites: [...] │ │
│ │ SNI: shop.example.com ← 평문 노출! │ │
│ │ Key Share: [...] │ │
│ └──────────────────────────────────────┘ │
└────────────────────────────────────────────┘
↑ 도청자가 접속 대상 도메인을 알 수 있음SNI의 가장 큰 이슈는, 호스트명이 평문으로 전송된다는 것입니다. TLS가 아직 수립되기 전이기 때문입니다.
프라이버시 수준
SNI (평문) ▓░░░░░░░░░░ 낮음 도메인 노출
ESNI (암호화) ▓▓▓▓▓▓░░░░░ 중간 CloudFlare 등 지원
ECH (전체 암호화) ▓▓▓▓▓▓▓▓▓▓ 높음 Client Hello 전체 암호화
(Inner/Outer 구조)ECH(Encrypted Client Hello)는 Client Hello 자체를 암호화하여, 도청자가 접속 대상을 알 수 없게 합니다. DNS HTTPS 레코드에서 서버의 공개키를 가져와 Client Hello를 암호화합니다.
mTLS (상호 인증)
일반적인 TLS에서는 서버만 인증서를 제시합니다. mTLS(Mutual TLS)는 서버뿐 아니라 클라이언트도 인증서를 제시하는 양방향 인증입니다.
일반 TLS (단방향)
Client ───────→ Server
"서버가 진짜인지 확인"
서버만 인증서 제시
사용자 인증: 별도 (ID/PW, 토큰 등)
mTLS (양방향)
Client ←──────→ Server
서버도 인증서 제시
클라이언트도 인증서 제시
"양쪽 모두 신원 확인"
과정
1. Server → Client: 서버 인증서 제시 (일반 TLS와 동일)
2. Server → Client: "Certificate Request" (너도 인증서 보내)
3. Client → Server: 클라이언트 인증서 제시
4. Server: 클라이언트 인증서 검증
5. 양방향 신뢰 확립| 사용 시나리오 | 설명 | 예시 |
|---|---|---|
| 마이크로서비스 | 서비스 간 상호 인증 | Istio, Linkerd 서비스 메시 |
| 금융 API | 핀테크 서비스 신원 증명 | Open Banking API |
| IoT | 기기 인증 | 스마트홈, 의료 기기 |
| Zero Trust | 네트워크 접근 제어 | BeyondCorp, Cloudflare Access |
| 내부 시스템 | 관리 도구 접근 제한 | DB 관리 콘솔 |
# 1. CA 개인키와 인증서 생성
openssl req -x509 -newkey rsa:4096 -nodes \
-keyout ca.key -out ca.crt -days 3650 \
-subj "/CN=My Internal CA"
# 2. 클라이언트 개인키와 CSR 생성
openssl req -newkey rsa:2048 -nodes \
-keyout client.key -out client.csr \
-subj "/CN=service-a"
# 3. CA로 클라이언트 인증서 서명
openssl x509 -req -in client.csr \
-CA ca.crt -CAkey ca.key -CAcreateserial \
-out client.crt -days 365
# 4. mTLS로 서버 접속 테스트
curl --cert client.crt --key client.key \
--cacert ca.crt https://internal-api.company.com/health도구 대상 특징
───────────────────────────────────────────────
cert-manager Kubernetes 자동 발급/갱신, Let's Encrypt 연동
Vault PKI 전체 인프라 HashiCorp, 동적 인증서 발급
SPIFFE/SPIRE 마이크로서비스 서비스 ID 기반, mTLS 자동화
AWS ACM AWS 서비스 무료, ALB/CloudFront 자동 적용다음 절에서는 네트워크에서 발생하는 보안 위협과 방어 전략을 살펴보겠습니다.