TLS / 暗号
通信を盗聴・改竄・なりすましから守る仕組み。HTTP の上に TLS が乗ると HTTPS になる。 対称鍵暗号 / 公開鍵暗号 / 証明書 / ハンドシェイクまで。
SSL と TLS
- SSL: Netscape 時代の古い名前。SSL 1.0/2.0/3.0 はすべて非推奨
- TLS: SSL の後継。現代の標準
- TLS 1.0 / 1.1 — 非推奨(ブラウザでブロック済)
- TLS 1.2 — まだ広く動く。互換性が必要な場面で
- TLS 1.3 — 現代の主流。安全で速い(RFC 8446)
TLS が提供する 3 つのこと
- 機密性(Confidentiality)— 盗聴されても読めない
- 完全性(Integrity)— 改竄されたら検出
- 認証(Authentication)— 通信相手が本物であることを確認
暗号の基礎
対称鍵暗号
- 暗号化と復号で同じ鍵を使う
- 速い、データ転送に向く
- 例: AES、ChaCha20
- 鍵をどう安全に共有するかが課題
公開鍵暗号(非対称)
- 公開鍵と秘密鍵のペア
- 公開鍵で暗号化 → 秘密鍵で復号
- 秘密鍵で署名 → 公開鍵で検証
- 遅い、鍵交換と署名に向く
- 例: RSA、ECDSA、Ed25519
ハイブリッド方式
実運用では「公開鍵暗号で対称鍵を交換 → 以降は対称鍵で通信」のハイブリッド。TLS もこれ。
ハッシュ関数
- 任意長 → 固定長の数値(指紋)
- SHA-256 / SHA-384 / BLAKE3
- MD5 / SHA-1 はもう使うな
HMAC / AEAD
- HMAC: 鍵付きハッシュ。改竄検知
- AEAD: 暗号化 + 認証を一括(AES-GCM、ChaCha20-Poly1305)。TLS 1.3 標準
鍵交換 (Key Exchange)
- RSA 鍵交換: 古典。Forward Secrecy なしのため非推奨
- (EC)DHE: Diffie-Hellman 鍵交換。Forward Secrecy あり。現代の標準
- X25519: 高速な楕円曲線。TLS 1.3 で広く使用
Forward Secrecy
サーバの秘密鍵が将来漏れても、過去の通信は復号できない性質。 現代の TLS では (EC)DHE で毎セッション一時鍵を作るので確保される。
証明書 (X.509)
サーバが「私は example.com です」と第三者の保証付きで示すための電子証明書。
含まれるもの
- 主体(CN / SAN: 対象ドメイン名)
- 発行者(CA)
- 有効期間
- サーバの公開鍵
- シリアル番号、用途、拡張
- CA による電子署名
SAN(Subject Alternative Name)
現代の証明書はCN ではなく SAN でドメインを指定。
example.com と www.example.com を 1 枚で扱う、ワイルドカードなど。
CA(認証局)
証明書を発行する第三者機関。OS / ブラウザにルート CA の証明書が組み込まれているのが起点。
主要 CA
- Let's Encrypt — 無料、自動更新(90 日)
- Google Trust Services
- DigiCert / Sectigo / GoDaddy
- ZeroSSL
証明書の種類
| 種類 | 検証レベル | 取得 |
|---|---|---|
| DV | ドメイン所有確認のみ | 無料・即発行 |
| OV | 組織情報も検証 | 有料・数日 |
| EV | 厳格な実体確認(緑バー時代) | 高価・数週間 |
現代のブラウザは EV を視覚的に区別しないため、DV で十分なことが多い。
信頼の連鎖
ルート CA(OS/ブラウザに組込)
|
中間 CA(CA が運用)
|
リーフ証明書(example.com の証明書)
サーバはリーフ + 中間を一緒に提示する必要がある(ルートはクライアントが持っている)。
TLS 1.3 ハンドシェイク(簡略)
Client Server
|--- ClientHello ----------------->|
| - 対応暗号スイート |
| - 鍵交換用の公開鍵 (X25519) |
| |
|<-- ServerHello ------------------|
| - 選んだ暗号スイート |
| - サーバの公開鍵 |
| - 証明書(暗号化済) |
| - Finished |
| |
|--- Finished ------------------->|
| |
|== 以降は対称鍵で暗号化された通信 ==|
- TLS 1.3 は 1-RTT でハンドシェイク完了
- 0-RTT 再開: 過去の鍵を使って即送信(リプレイ耐性に注意)
- TLS 1.2 は 2-RTT 必要だった
SNI(Server Name Indication)
クライアントが「これからアクセスするドメイン名」を ClientHello に含める拡張。 1 IP / 複数ドメインのサーバが、適切な証明書を返せるようになる。
ESNI / ECH
SNI は平文なので「どのドメインに繋ぐか」が中間者に見える問題があった。 Encrypted Client Hello (ECH) で SNI も暗号化されつつある(Cloudflare / Firefox 等)。
OCSP / CRL
証明書の失効確認。
- CRL: 失効リストを定期取得(古典)
- OCSP: 1 件ごとに CA に問合せ(プライバシー懸念)
- OCSP Stapling: サーバが OCSP 応答をキャッシュしてクライアントに渡す(推奨)
- Must-Staple: 拡張で「stapling 必須」を表明
HSTS
「以降このドメインは HTTPS 必須」とブラウザに教えるヘッダ。HTTP への降格攻撃を防ぐ。
Strict-Transport-Security: max-age=63072000; includeSubDomains; preload
HSTS Preload List に登録するとブラウザに最初から組込まれる(Chrome / Firefox / Edge / Safari)。
クライアント認証 (mTLS)
通常はサーバだけ証明書を提示するが、mTLS ではクライアントも証明書を提示する。 社内 API / IoT / Zero Trust ネットワークで使用。
暗号スイート
TLS で使う暗号アルゴリズムの組み合わせ。
TLS_AES_128_GCM_SHA256 ← TLS 1.3
└─┘└─────────┘└────┘
TLS / AEAD / ハッシュ
- TLS 1.3 はスイートが整理されてシンプル
- TLS 1.2 は
ECDHE-RSA-AES256-GCM-SHA384のように長い
Let's Encrypt の使い方
# certbot
sudo certbot certonly --standalone -d example.com -d www.example.com
# ACME クライアント (acme.sh)
acme.sh --issue -d example.com --webroot /var/www
# 自動更新(cron)
0 3 * * * /usr/bin/certbot renew --quiet
多くのプラットフォーム(Cloudflare / Vercel / Netlify / Cloudflare Pages)は自動 TLS。 意識せずに HTTPS が動く。
OpenSSL でデバッグ
# サーバの証明書を見る
openssl s_client -connect example.com:443 -servername example.com
# 証明書の中身
openssl x509 -in cert.pem -text -noout
# 自己署名証明書を作る(開発用)
openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem \
-days 365 -nodes -subj "/CN=localhost"
# CSR を作る
openssl req -new -newkey rsa:2048 -nodes -keyout key.pem -out req.csr
HTTPS 化のメリット
- 盗聴・改竄防止
- HTTP/2、HTTP/3 はほぼ HTTPS 必須
- Service Worker / WebRTC / Geolocation など多くの API が HTTPS 必須
- SEO で優遇
- ユーザの信頼
その他の暗号通信
- SSH: リモートシェル。公開鍵認証 + 暗号化
- IPsec: IP 層で暗号化(VPN)
- WireGuard: 現代的な VPN プロトコル。シンプル・高速
- Noise Protocol: TLS よりシンプルなフレームワーク(WhatsApp / Signal 内部)
WebCrypto API
ブラウザで暗号操作ができる標準 API。
// SHA-256 ハッシュ
const buf = new TextEncoder().encode("hello")
const hash = await crypto.subtle.digest("SHA-256", buf)
const hex = Array.from(new Uint8Array(hash))
.map(b => b.toString(16).padStart(2, "0")).join("")
// AES-GCM 暗号化
const key = await crypto.subtle.generateKey(
{ name: "AES-GCM", length: 256 },
true,
["encrypt", "decrypt"]
)
const iv = crypto.getRandomValues(new Uint8Array(12))
const enc = await crypto.subtle.encrypt(
{ name: "AES-GCM", iv },
key,
buf
)
失敗パターン
| 症状 | 原因 |
|---|---|
| NET::ERR_CERT_DATE_INVALID | 有効期限切れ / 端末時計ズレ |
| NET::ERR_CERT_AUTHORITY_INVALID | 自己署名 or 信頼されない CA |
| NET::ERR_CERT_COMMON_NAME_INVALID | SAN にドメインが入っていない |
| HSTS で繋がらない | 過去の HSTS が残っている。chrome://net-internals/#hsts で削除 |
| 中間証明書欠落 | サーバが fullchain を返していない |
| 古い暗号スイート | TLS 1.0 / 1.1 を使ってる |
SSL Labs / 検査ツール
- SSL Labs SSL Test — A+ を目指す
- HSTS Preload 登録
- crt.sh — 証明書透明性ログ検索
- securityheaders.com
現代の Web はHTTPS 一択。Let's Encrypt や CDN の自動 TLS で 実装コストはほぼゼロ。HSTS / OCSP Stapling / TLS 1.3 を有効にしておくのが基準ライン。