C
セキュリティ/暗号化 認証/Lesson 03

暗号化 + 認証 — ハッシュ・HTTPS・セッション/JWT・クッキー

45分·theory

暗号化 + 認証 — ハッシュ・HTTPS・セッション/JWT・クッキー

🎯 このレッスンを読み終えたら

このレッスンをすべて読み終えると、以下の3つを自信を持って扱えるようになります。

  • ✅ bcrypt vs argon2 (MD5/SHA-1は禁止)
  • ✅ JWTの署名アルゴリズム (HS256 vs RS256)
  • ✅ OAuth 2.0の4つのフロー (Code · PKCE · Client Credentials · Refresh)

学習目標をチェックリストとして手元に置き、すべて答えられるようになったらレッスンを閉じてください。

パスワードのハッシング — bcrypt · argon2 (平文は絶対NG)

一言で: パスワードはハッシュとして保存します(逆変換不可)。毎回異なるソルトを追加します。

ハッシュアルゴリズムの選択:

アルゴリズム推奨?備考
bcrypt事実上の標準。コストファクター 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 で数時間以内に解読可能
  • ❌ ソルトなし → 同じパスワードが同じハッシュになる
  • ❌ パスワード長の制限(8文字)— むしろ長く設定する(16文字以上のパスフレーズ)
  • ✅ bcrypt + コスト12 + ソルト自動付与(ライブラリが処理)

HTTPS · TLS 1.3 — 通信の暗号化

なぜ必要か: 平文の HTTP は、共有 Wi-Fi などのネットワーク上でパケットが傍受されると、パスワードやセッショントークンが漏洩します。

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 再接続なしサポート (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 を検索 → ユーザーを認証

セッション vs JWT の比較:

項目セッション (Cookie)JWT (Bearer Token)
保存場所サーバー (Redis/DB)クライアント (LocalStorage · Cookie)
無効化サーバーで削除 → 即時有効期限まで有効(ブラックリストが必要)
サイズsessionId 約32文字JWT 200〜500文字
スケーラビリティRedis が必要ステートレス
セキュリティ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 — 他サービスへの認証委任(Google · Kakao でログイン):
1. ユーザー: 「Google でログイン」をクリック
2. Google 認証ページ → ユーザーが同意
3. Google → 自社サーバーに authorization code を送信
4. 自社サーバー: code → access_token を交換(サーバー間通信)
5. token で Google API を呼び出し(ユーザーのメールアドレスと名前を取得)
6. 自社 DB にユーザーを作成またはログイン

> 💡 PKCE の追加を強く推奨(モバイル · SPA): code が中間者に傍受されても安全。

💻 📌 セキュリティヘッダー + レート制限
// === 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,               // 1分あたり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 にこう依頼してみましょう

このレッスンの概念を理解すると、AI に具体的に指示できるようになります。漠然とした「直して」ではなく、語彙を持ったリクエスト — それがトークン節約の出発点です。

  • 「このパスワードハッシングを MD5 から bcrypt に移行して」
  • 「JWT の secret を HS256 から RS256(非対称)に変更する手順を教えて」

なぜこれがトークンを減らすのか

概念を知らないと、AI の回答を受け取っても 「それって何ですか?」 と再度聞き直す必要があります。その「聞き直し」がトークンを消費します。概念を一度身につけておけば、会話が一度で完結します。

暗号化 + 認証 — ハッシュ・HTTPS・セッション・JWT - セキュリティ