C
보안/암호화_인증/Lesson 03

암호화 + 인증 — 해시·HTTPS·세션/JWT·쿠키

45분·theory

암호화 + 인증 — 해시·HTTPS·세션/JWT·쿠키

🎯 이 lesson 을 읽고 나면

이 lesson 을 다 읽고 나면 아래 3가지를 자신 있게 할 수 있습니다.

  • ✅ bcrypt vs argon2 (MD5/SHA-1 금지)
  • ✅ JWT 의 서명 알고리즘 (HS256 vs RS256)
  • ✅ OAuth 2.0 흐름 4가지 (Code · PKCE · Client Credentials · Refresh)

학습 목표를 체크리스트로 두고 다 답할 수 있게 되면 lesson 을 닫으세요.

비번 해싱 — bcrypt · argon2 (절대 평문 X)

한 줄: 비번은 해시로 저장 (역변환 불가). 매번 다른 salt 추가.

해시 알고리즘 선택:

알고리즘권장?비고
bcrypt사실상 표준. cost factor 10-12
argon2✅✅2015 PHC 우승작. 메모리 비용까지
scrypt메모리 비용. 덜 표준
PBKDF2구식·약함 (NIST 만 권장)
MD5·SHA1❌❌즉시 폐기. GPU 로 초당 수십억 시도
SHA-256 (단독)빠름 = 무차별 대입 쉬움

bcrypt 사용 예 (Spring):

java
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder(12);
String hash = encoder.encode(plainPassword);       // 저장 시
boolean ok = encoder.matches(plain, storedHash);   // 검증 시

해싱 함정:

  • ❌ MD5(비번) → 무지개 테이블로 즉시 복원
  • ❌ SHA256(비번) → GPU 로 수 시간 안에
  • ❌ Salt 없음 → 같은 비번끼리 같은 해시
  • ❌ 비번 길이 제한 (8자) — 차라리 길게 (16자+ passphrase)
  • ✅ bcrypt + cost 12 + salt 자동 (라이브러리가 처리)

HTTPS · TLS 1.3 — 통신 암호화

왜 필요한가: 평문 HTTP = 와이파이 같은 망에서 패킷 탈취 = 비번·세션 토큰 노출.

TLS 1.3 핸드셰이크 (1-RTT, 2018+ 표준):
1. Client Hello — 지원 암호 알고리즘 목록 + 키 공유 시작
2. Server Hello + 인증서 — 선택한 알고리즘 + 서버 공개키 인증서
3. 검증 — 클라이언트가 인증서 체인 확인 (CA → 중간 → 서버)
4. 세션 키 도출 — Diffie-Hellman 으로 공유 비밀키 생성
5. 암호화 통신 시작 — AES-GCM·ChaCha20

TLS 1.2 vs 1.3 차이:

항목TLS 1.2TLS 1.3
핸드셰이크2-RTT1-RTT
Forward Secrecy옵션필수
약한 암호다수모두 제거
0-RTT 재접속X지원 (PSK)

TLS 함정:

  • ❌ Let's Encrypt 인증서 만료 (90일) — 자동 갱신 필수
  • ❌ TLS 1.0/1.1 활성 — 2020 폐기됨
  • ❌ Mixed Content (HTTPS 페이지에서 HTTP 리소스) — 브라우저 차단
  • ✅ HSTS 헤더로 HTTPS 강제: Strict-Transport-Security: max-age=31536000

인증 — 세션 vs JWT vs OAuth

로그인 흐름 (세션 기반, 가장 흔함):
1. 사용자: POST /login (이메일 + 비번)
2. 서버: bcrypt.matches → 성공
3. 서버: sessionId 생성 + Redis/DB 저장
4. 응답: Set-Cookie: sessionId=abc; HttpOnly; Secure; SameSite=Lax
5. 이후 요청: 브라우저가 자동으로 쿠키 첨부
6. 서버: sessionId → Redis 조회 → user 인증

세션 vs JWT 비교:

항목세션 (Cookie)JWT (Bearer Token)
저장 위치서버 (Redis/DB)클라이언트 (LocalStorage·Cookie)
무효화서버에서 삭제 → 즉시만료까지 유효 (블랙리스트 필요)
사이즈sessionId 32자JWT 200-500자
확장성Redis 필요Stateless
보안HttpOnly 쿠키 = XSS 안전LocalStorage = XSS 위험
모바일쿠키 다루기 까다로움Authorization 헤더 깔끔

쿠키 보안 설정 (반드시):

code
Set-Cookie: sessionId=abc;
  HttpOnly;          // JS 에서 접근 X (XSS 방어)
  Secure;            // HTTPS 에서만 전송
  SameSite=Lax;      // CSRF 방어 (외부 사이트에서 쿠키 X)
  Max-Age=86400;     // 1일
  Path=/;

OAuth 2.0 — 다른 사이트의 인증 위임 (구글·카카오로 로그인):
1. 사용자: "Google 로 로그인" 클릭
2. Google 인증 페이지 → 사용자 동의
3. Google → 우리 서버에 authorization code
4. 우리 서버: code → access_token 교환 (server-to-server)
5. token 으로 Google API 호출 (사용자 이메일·이름 조회)
6. 우리 DB 에 사용자 생성·로그인

> 💡 PKCE 추가 권장 (모바일·SPA): code 가 중간자에 노출되어도 안전.

💻 📌 보안 헤더 + Rate Limiting
// === Node.js Express — 보안 헤더 + Rate Limit ===
const express = require('express');
const helmet = require('helmet');
const rateLimit = require('express-rate-limit');

const app = express();

// 1. 보안 헤더 자동 설정 (CSP·HSTS·X-Frame·X-XSS 등)
app.use(helmet({
  contentSecurityPolicy: {
    directives: {
      defaultSrc: ["'self'"],
      scriptSrc: ["'self'", "'unsafe-inline'"],  // React inline script 허용
      styleSrc: ["'self'", "'unsafe-inline'"],
      imgSrc: ["'self'", 'data:', 'https://cdn.example.com'],
    },
  },
  hsts: { maxAge: 31536000, includeSubDomains: true, preload: true },
}));

// 2. Rate Limit — 무차별 대입 방어
const loginLimiter = rateLimit({
  windowMs: 15 * 60 * 1000,    // 15분 윈도우
  max: 5,                      // 같은 IP 가 15분에 5회만
  message: '로그인 시도가 너무 많습니다. 15분 후 다시 시도하세요.',
  standardHeaders: true,
  legacyHeaders: false,
});
app.post('/login', loginLimiter, async (req, res) => { /* ... */ });

const apiLimiter = rateLimit({
  windowMs: 60 * 1000,    // 1분
  max: 100,               // 분당 100 요청
});
app.use('/api/', apiLimiter);

// === Redis 기반 Rate Limit (분산 환경) ===
// const RedisStore = require('rate-limit-redis');
// const Redis = require('ioredis');
// const client = new Redis();
// const limiter = rateLimit({
//   store: new RedisStore({ sendCommand: (...args) => client.call(...args) }),
//   windowMs: 60_000, max: 100,
// });

// === 쿠키·세션 보안 ===
const session = require('express-session');
const RedisStore2 = require('connect-redis').default;
app.use(session({
  store: new RedisStore2({ client: new Redis() }),
  secret: process.env.SESSION_SECRET,
  resave: false,
  saveUninitialized: false,
  cookie: {
    httpOnly: true,        // XSS 방어
    secure: true,          // HTTPS 에서만
    sameSite: 'lax',       // CSRF 방어
    maxAge: 24 * 60 * 60 * 1000,
  },
}));

🤖 AI 에게 이렇게 요청해보세요

이 lesson 의 개념을 알면 AI 에게 구체적으로 지시할 수 있습니다. 막연한 "고쳐줘" 가 아니라 어휘를 가진 요청 — 그게 토큰 절약의 출발점입니다.

  • "이 비밀번호 해싱을 MD5 → bcrypt 로 마이그레이션해줘"
  • "JWT secret 을 HS256 → RS256 (비대칭) 으로 바꾸는 단계 알려줘"

왜 이게 토큰을 줄이나

개념을 모를 땐 AI 답변을 받고도 "그게 뭐예요?" 를 다시 물어야 합니다. 그 "다시 물음" 이 토큰을 잡아먹습니다. 개념 한 번 익혀두면 대화가 한 번에 끝납니다.

암호화 + 인증 — 해시·HTTPS·세션·JWT - 보안