🔐 SSL 인증서 설정할 때마다 PEM, CRT, KEY 파일 때문에 헷갈린다고요? 이 글 하나면 보안 인증서의 모든 것을 완벽하게 이해할 수 있어요!
웹 개발이나 시스템 관리를 하다 보면 반드시 마주치는 것이 바로 PEM, CRT, KEY 파일입니다. HTTPS 설정, SSH 접속, API 인증… 디지털 보안의 핵심에는 항상 이런 파일들이 있죠.
하지만 이 파일들이 정확히 무엇인지, 어떤 차이가 있는지, 실제로 어떻게 사용해야 하는지 명확하게 아는 사람은 의외로 많지 않습니다. 이 글을 읽고 나면 보안 인증서의 전문가가 될 수 있을 거예요!
🎯 PEM, CRT, KEY 파일이란? 기본 개념 완전 정복
디지털 인증서의 기본 원리
인터넷에서의 보안 통신은 공개키 암호화 방식을 기반으로 합니다. 이는 수학적으로 연결된 두 개의 키(공개키와 개인키)를 사용하는 방식이에요.
공개키 암호화의 작동 원리
[클라이언트] ←→ [서버]
↑ ↑
공개키로 암호화 개인키로 복호화
- 서버가 키 쌍 생성 (공개키 + 개인키)
- 공개키를 인증서에 포함하여 클라이언트에 전송
- 클라이언트가 공개키로 데이터 암호화
- 서버가 개인키로 데이터 복호화
인증서 파일 형식의 역사와 필요성
왜 이렇게 많은 파일 형식이 존재할까요?
시대 | 주요 형식 | 특징 | 사용처 |
---|---|---|---|
1980년대 | PEM | 텍스트 기반, 이메일 전송 가능 | 초기 인터넷 통신 |
1990년대 | DER, CRT | 바이너리, 효율적 저장 | 웹 서버, 브라우저 |
2000년대 | P12, PFX | 인증서+키 통합 | 윈도우 환경 |
2010년대~ | JWK, JOSE | JSON 기반, API 친화적 | 현대적 웹 서비스 |
🔑 PEM 파일: 인증서 세계의 만능 포맷
PEM (Privacy Enhanced Mail)이란?
PEM은 1987년에 개발된 가장 오래되고 널리 사용되는 인증서 형식입니다. 이름에서 알 수 있듯이 원래는 이메일 보안을 위해 만들어졌지만, 현재는 거의 모든 곳에서 사용되고 있어요.
PEM 파일의 특징
-----BEGIN CERTIFICATE-----
MIIDXTCCAkWgAwIBAgIJAKoK/heBjcOuMA0GCSqGSIb3DQEBCwUAMEUxCzAJBgNV
BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX
aWRnaXRzIFB0eSBMdGQwHhcNMTcwMzI3MDkxODIyWhcNMTgwMzI3MDkxODIyWjBF
... (Base64 인코딩된 데이터) ...
-----END CERTIFICATE-----
핵심 특성:
- Base64 인코딩: 바이너리 데이터를 텍스트로 변환
- ASCII 텍스트: 메모장으로도 열어볼 수 있음
- 헤더/푸터 구조: BEGIN/END로 구분
- 체인 지원: 여러 인증서를 하나 파일에 저장 가능
PEM 파일에 포함될 수 있는 내용들
# 1. 인증서
-----BEGIN CERTIFICATE-----
[인증서 데이터]
-----END CERTIFICATE-----
# 2. 개인키 (암호화되지 않은)
-----BEGIN PRIVATE KEY-----
[개인키 데이터]
-----END PRIVATE KEY-----
# 3. 개인키 (암호화된)
-----BEGIN ENCRYPTED PRIVATE KEY-----
[암호화된 개인키 데이터]
-----END ENCRYPTED PRIVATE KEY-----
# 4. RSA 개인키
-----BEGIN RSA PRIVATE KEY-----
[RSA 개인키 데이터]
-----END RSA PRIVATE KEY-----
# 5. 인증서 요청서 (CSR)
-----BEGIN CERTIFICATE REQUEST-----
[CSR 데이터]
-----END CERTIFICATE REQUEST-----
# 6. 공개키
-----BEGIN PUBLIC KEY-----
[공개키 데이터]
-----END PUBLIC KEY-----
PEM 파일 실무 활용법
1. PEM 파일 내용 확인하기
# 인증서 정보 확인
openssl x509 -in certificate.pem -text -noout
# 개인키 정보 확인
openssl rsa -in private-key.pem -text -noout
# 인증서 만료일 확인
openssl x509 -in certificate.pem -noout -dates
# 인증서와 개인키 매칭 확인
openssl x509 -noout -modulus -in certificate.pem | openssl md5
openssl rsa -noout -modulus -in private-key.pem | openssl md5
2. 다른 형식으로 변환하기
# PEM → DER 변환
openssl x509 -outform der -in certificate.pem -out certificate.der
# DER → PEM 변환
openssl x509 -inform der -in certificate.der -out certificate.pem
# P12 → PEM 변환
openssl pkcs12 -in certificate.p12 -out certificate.pem -nodes
📜 CRT 파일: 신뢰할 수 있는 디지털 신분증
CRT (Certificate) 파일의 정체
CRT 파일은 X.509 표준을 따르는 디지털 인증서입니다. 실제로는 PEM 형식으로 저장되는 경우가 대부분이지만, 파일 확장자로 용도를 명확히 구분하기 위해 사용돼요.
X.509 인증서의 구조
X.509 인증서 = {
버전 정보
시리얼 번호
서명 알고리즘
발급자 (Issuer) 정보
유효 기간 (Not Before, Not After)
주체 (Subject) 정보
공개키 정보
확장 필드 (Subject Alternative Name 등)
디지털 서명
}
CRT 파일에서 추출할 수 있는 중요 정보
# 인증서 전체 정보 보기
openssl x509 -in server.crt -text -noout
# 주요 정보만 추출
# 1. 발급자 정보
openssl x509 -in server.crt -noout -issuer
# 2. 주체 정보 (도메인명)
openssl x509 -in server.crt -noout -subject
# 3. SAN (Subject Alternative Names)
openssl x509 -in server.crt -noout -ext subjectAltName
# 4. 유효 기간
openssl x509 -in server.crt -noout -dates
# 5. 지문 (Fingerprint)
openssl x509 -in server.crt -noout -fingerprint -sha256
실제 출력 예시:
issuer=C = US, O = Let's Encrypt, CN = R3
subject=CN = example.com
notBefore=Jan 1 00:00:00 2025 GMT
notAfter=Apr 1 23:59:59 2025 GMT
DNS:example.com, DNS:www.example.com
인증서 체인의 이해
인증서 체인이란?
[루트 CA] ← 최상위 신뢰 기관
↓
[중간 CA] ← 중간 인증 기관
↓
[서버 인증서] ← 실제 사용하는 인증서
왜 체인이 필요할까?
- 보안: 루트 CA 개인키를 안전하게 보호
- 확장성: 중간 CA를 통한 분산 관리
- 신뢰성: 계층적 신뢰 구조 구축
인증서 체인 파일 생성하기
# 개별 인증서 파일들을 체인으로 결합
cat server.crt intermediate.crt root.crt > fullchain.pem
# 또는 서버 인증서 + 중간 인증서만 (권장)
cat server.crt intermediate.crt > chain.pem
# 체인 검증
openssl verify -CAfile root.crt -untrusted intermediate.crt server.crt
🗝️ KEY 파일: 보안의 핵심, 개인키
개인키의 중요성
개인키는 디지털 보안의 핵심입니다. 이 키가 유출되면 해당 인증서로 보호되는 모든 통신이 위험에 노출됩니다.
개인키 파일의 종류
# 1. 암호화되지 않은 개인키 (위험!)
-----BEGIN PRIVATE KEY-----
[개인키 데이터]
-----END PRIVATE KEY-----
# 2. 암호화된 개인키 (안전)
-----BEGIN ENCRYPTED PRIVATE KEY-----
Proc-Type: 4,ENCRYPTED
DEK-Info: AES-256-CBC,A1B2C3D4E5F6...
[암호화된 개인키 데이터]
-----END ENCRYPTED PRIVATE KEY-----
# 3. RSA 개인키 (레거시)
-----BEGIN RSA PRIVATE KEY-----
[RSA 개인키 데이터]
-----END RSA PRIVATE KEY-----
개인키 보안 관리 방법
# 1. 개인키 권한 설정 (매우 중요!)
chmod 600 private.key # 소유자만 읽기/쓰기
chown root:root private.key # 루트 소유
# 2. 개인키 암호화
openssl rsa -aes256 -in private.key -out private-encrypted.key
# 3. 암호화된 개인키 사용 (비밀번호 입력 필요)
openssl rsa -in private-encrypted.key -out private.key
# Enter pass phrase for private-encrypted.key: ****
# 4. 개인키에서 공개키 추출
openssl rsa -in private.key -pubout -out public.key
키 쌍 생성 실습
RSA 키 쌍 생성
# 2048비트 RSA 키 쌍 생성
openssl genrsa -out private.key 2048
# 4096비트 RSA 키 쌍 생성 (더 안전)
openssl genrsa -out private.key 4096
# 암호화된 RSA 키 쌍 생성
openssl genrsa -aes256 -out private-encrypted.key 2048
ECC (타원 곡선) 키 쌍 생성
# 사용 가능한 곡선 목록 확인
openssl ecparam -list_curves
# P-256 곡선을 사용한 ECC 키 생성
openssl ecparam -genkey -name prime256v1 -noout -out private-ecc.key
# P-384 곡선을 사용한 ECC 키 생성 (더 안전)
openssl ecparam -genkey -name secp384r1 -noout -out private-ecc384.key
RSA vs ECC 비교:
특성 | RSA 2048bit | ECC P-256 | ECC P-384 |
---|---|---|---|
보안 강도 | 112bit | 128bit | 192bit |
키 크기 | 2048bit | 256bit | 384bit |
성능 | 느림 | 빠름 | 빠름 |
호환성 | 매우 높음 | 높음 | 높음 |
🌐 실제 사용 사례: 실무에서의 활용법
사례 1: 웹서버 SSL/TLS 설정
Apache 웹서버 설정
# /etc/httpd/conf.d/ssl.conf 또는 가상호스트 설정
<VirtualHost *:443>
ServerName example.com
ServerAlias www.example.com
# SSL 엔진 활성화
SSLEngine on
# 인증서 파일 지정
SSLCertificateFile /etc/ssl/certs/example.com.crt
# 개인키 파일 지정
SSLCertificateKeyFile /etc/ssl/private/example.com.key
# 중간 인증서 체인 지정
SSLCertificateChainFile /etc/ssl/certs/intermediate.crt
# 또는 전체 체인을 하나 파일로
# SSLCertificateFile /etc/ssl/certs/fullchain.pem
# 보안 설정
SSLProtocol all -SSLv2 -SSLv3 -TLSv1 -TLSv1.1
SSLCipherSuite ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384
SSLHonorCipherOrder on
</VirtualHost>
Nginx 웹서버 설정
# /etc/nginx/sites-available/example.com
server {
listen 443 ssl http2;
server_name example.com www.example.com;
# 전체 인증서 체인 (서버 인증서 + 중간 인증서)
ssl_certificate /etc/ssl/certs/fullchain.pem;
# 개인키
ssl_certificate_key /etc/ssl/private/private.key;
# 보안 설정
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384;
ssl_prefer_server_ciphers off;
# HSTS 헤더
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
# 나머지 설정...
}
# HTTP → HTTPS 리다이렉트
server {
listen 80;
server_name example.com www.example.com;
return 301 https://$server_name$request_uri;
}
사례 2: SSH 키 인증 설정
SSH 키 쌍 생성 및 설정
# 1. SSH 키 쌍 생성 (RSA)
ssh-keygen -t rsa -b 4096 -m PEM -f ~/.ssh/id_rsa_server
# 2. SSH 키 쌍 생성 (Ed25519 - 권장)
ssh-keygen -t ed25519 -f ~/.ssh/id_ed25519_server
# 3. 공개키를 서버에 복사
ssh-copy-id -i ~/.ssh/id_ed25519_server.pub user@server.example.com
# 4. SSH 클라이언트 설정 (~/.ssh/config)
Host myserver
HostName server.example.com
User myuser
IdentityFile ~/.ssh/id_ed25519_server
IdentitiesOnly yes
SSH 서버 보안 강화 설정
# /etc/ssh/sshd_config 편집
# 호스트 키 지정
HostKey /etc/ssh/ssh_host_rsa_key
HostKey /etc/ssh/ssh_host_ecdsa_key
HostKey /etc/ssh/ssh_host_ed25519_key
# 보안 설정
PasswordAuthentication no
PubkeyAuthentication yes
PermitRootLogin no
Protocol 2
# 사용할 키 교환 알고리즘 제한
KexAlgorithms curve25519-sha256@libssh.org,ecdh-sha2-nistp521,ecdh-sha2-nistp384
# 서비스 재시작
systemctl restart sshd
사례 3: Docker/Kubernetes에서의 인증서 관리
Docker 컨테이너 SSL 설정
# Dockerfile
FROM nginx:alpine
# 인증서 파일 복사
COPY ssl/fullchain.pem /etc/ssl/certs/
COPY ssl/private.key /etc/ssl/private/
COPY nginx.conf /etc/nginx/nginx.conf
# 권한 설정
RUN chmod 644 /etc/ssl/certs/fullchain.pem && \
chmod 600 /etc/ssl/private/private.key && \
chown root:root /etc/ssl/private/private.key
EXPOSE 80 443
CMD ["nginx", "-g", "daemon off;"]
Kubernetes Secret을 이용한 인증서 관리
# tls-secret.yaml
apiVersion: v1
kind: Secret
metadata:
name: tls-secret
namespace: default
type: kubernetes.io/tls
data:
tls.crt: |
# Base64 인코딩된 인증서 (fullchain.pem)
LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0t...
tls.key: |
# Base64 인코딩된 개인키
LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0t...
---
# ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: web-ingress
spec:
tls:
- hosts:
- example.com
- www.example.com
secretName: tls-secret
rules:
- host: example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: web-service
port:
number: 80
인증서를 Secret으로 생성하기
# 커맨드로 TLS Secret 생성
kubectl create secret tls tls-secret \
--cert=fullchain.pem \
--key=private.key
# 파일에서 일반 Secret 생성
kubectl create secret generic app-certs \
--from-file=server.crt=server.crt \
--from-file=server.key=server.key \
--from-file=ca.crt=ca.crt
# Secret 확인
kubectl get secrets
kubectl describe secret tls-secret
사례 4: API 클라이언트 인증
Java에서 클라이언트 인증서 사용
// Java KeyStore 생성
import java.security.KeyStore;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
public class ClientCertAuth {
public static void setupClientCert() throws Exception {
// PKCS12 형식의 클라이언트 인증서 로드
KeyStore keyStore = KeyStore.getInstance("PKCS12");
keyStore.load(new FileInputStream("client.p12"), "password".toCharArray());
// KeyManagerFactory 설정
KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
kmf.init(keyStore, "password".toCharArray());
// SSL 컨텍스트 생성
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(kmf.getKeyManagers(), null, null);
// 기본 SSL 컨텍스트로 설정
HttpsURLConnection.setDefaultSSLSocketFactory(
sslContext.getSocketFactory());
}
}
Python에서 클라이언트 인증서 사용
import requests
import ssl
# 클라이언트 인증서를 사용한 HTTPS 요청
def api_call_with_cert():
# 인증서와 키 파일 지정
cert_file = '/path/to/client.crt'
key_file = '/path/to/client.key'
# requests를 이용한 호출
response = requests.get(
'https://api.example.com/secure-endpoint',
cert=(cert_file, key_file),
verify='/path/to/ca.crt' # 서버 인증서 검증용 CA
)
return response.json()
# urllib를 이용한 방법
import urllib.request
import ssl
def api_call_with_urllib():
# SSL 컨텍스트 생성
context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
context.load_cert_chain('/path/to/client.crt', '/path/to/client.key')
context.load_verify_locations('/path/to/ca.crt')
# 요청 실행
request = urllib.request.Request('https://api.example.com/secure-endpoint')
response = urllib.request.urlopen(request, context=context)
return response.read()
🔧 인증서 생성부터 배포까지 완전 가이드
자체 서명 인증서 (Self-Signed Certificate) 생성
개발/테스트용 자체 서명 인증서
# 1. 개인키 생성
openssl genrsa -out selfsigned.key 2048
# 2. 인증서 서명 요청(CSR) 생성
openssl req -new -key selfsigned.key -out selfsigned.csr \
-subj "/C=KR/ST=Seoul/L=Seoul/O=MyCompany/CN=localhost"
# 3. 자체 서명 인증서 생성 (1년 유효)
openssl x509 -req -in selfsigned.csr -signkey selfsigned.key \
-out selfsigned.crt -days 365
# 4. 한 번에 생성하기
openssl req -x509 -newkey rsa:2048 -nodes -keyout selfsigned.key \
-out selfsigned.crt -days 365 \
-subj "/C=KR/ST=Seoul/L=Seoul/O=MyCompany/CN=localhost"
SAN(Subject Alternative Names) 포함한 인증서
# config 파일 생성 (san.conf)
cat > san.conf << EOF
[req]
default_bits = 2048 prompt = no distinguished_name = req_distinguished_name req_extensions = v3_req
[req_distinguished_name]
C = KR ST = Seoul L = Seoul O = MyCompany CN = example.local
[v3_req]
basicConstraints = CA:FALSE keyUsage = nonRepudiation, digitalSignature, keyEncipherment subjectAltName = @alt_names
[alt_names]
DNS.1 = example.local DNS.2 = www.example.local DNS.3 = api.example.local IP.1 = 127.0.0.1 IP.2 = 192.168.1.100 EOF # SAN이 포함된 자체 서명 인증서 생성 openssl req -x509 -nodes -days 365 -newkey rsa:2048 \ -keyout selfsigned.key -out selfsigned.crt \ -config san.conf -extensions v3_req
Let’s Encrypt 무료 SSL 인증서
Certbot을 이용한 자동 발급
# Certbot 설치 (Ubuntu/Debian)
sudo apt update
sudo apt install certbot python3-certbot-nginx
# Nginx용 인증서 자동 발급 및 설정
sudo certbot --nginx -d example.com -d www.example.com
# Apache용 인증서 자동 발급 및 설정
sudo certbot --apache -d example.com -d www.example.com
# 수동 인증 (DNS 챌린지)
sudo certbot certonly --manual --preferred-challenges dns \
-d example.com -d www.example.com
# 와일드카드 인증서 발급
sudo certbot certonly --manual --preferred-challenges dns \
-d "*.example.com" -d example.com
인증서 자동 갱신 설정
# 갱신 테스트
sudo certbot renew --dry-run
# 크론잡으로 자동 갱신 설정 (매일 2번 체크)
sudo crontab -e
# 다음 라인 추가:
0 12,0 * * * /usr/bin/certbot renew --quiet && systemctl reload nginx
상용 SSL 인증서 발급 과정
CSR(Certificate Signing Request) 생성
# 1. 개인키 생성
openssl genrsa -out example.com.key 2048
# 2. CSR 생성 (대화형)
openssl req -new -key example.com.key -out example.com.csr
# 3. CSR 생성 (비대화형)
openssl req -new -key example.com.key -out example.com.csr \
-subj "/C=KR/ST=Seoul/L=Seoul/O=MyCompany/CN=example.com"
# 4. 다중 도메인 CSR 생성
cat > multi-domain.conf << EOF
[req]
default_bits = 2048 prompt = no distinguished_name = req_distinguished_name req_extensions = v3_req
[req_distinguished_name]
C = KR ST = Seoul L = Seoul O = MyCompany CN = example.com
[v3_req]
basicConstraints = CA:FALSE keyUsage = nonRepudiation, digitalSignature, keyEncipherment subjectAltName = @alt_names
[alt_names]
DNS.1 = example.com DNS.2 = www.example.com DNS.3 = api.example.com DNS.4 = admin.example.com EOF openssl req -new -key example.com.key -out example.com.csr \ -config multi-domain.conf
CSR 정보 확인
# CSR 내용 확인
openssl req -text -noout -in example.com.csr
# CSR의 공개키와 개인키 매칭 확인
openssl req -noout -modulus -in example.com.csr | openssl md5
openssl rsa -noout -modulus -in example.com.key | openssl md5
🛡️ 보안 관리와 베스트 프랙티스
개인키 보안 강화
파일 시스템 보안
# 개인키 파일 권한 설정
chmod 600 /path/to/private.key # rw------- (소유자만 읽기/쓰기)
chown root:root /path/to/private.key # 루트 소유
# 디렉토리 권한 설정
chmod 700 /etc/ssl/private/ # rwx------ (소유자만 접근)
chown root: