1-3. HTTPSとTLS - 通信路を盗聴・改ざんから守る
所要時間: 30-50分 ゴール: HTTPSの仕組み(TLSハンドシェイク・証明書チェーン・CA)を概念レベルで説明できる。MITM、HSTS、Let’s Encryptを理解する コミット内容: 自サービスのドメインを
openssl s_clientで覗いて証明書情報を~/learn/security/day03_tls/に保存
前章とのつながり
このレッスンの位置づけ
1-2 パスワード保存 で bcrypt でパスワードハッシュを完璧に守った。でも、ログイン時にパスワードがネットワークを平文で流れていたら? 同じカフェの隣の人が pcap で全部見ている。
サーバー側の守りと通信路の守りは 両輪。片方欠ければ他方も無意味。
- 次章 1-4 OWASP Top 10: A02 (Cryptographic Failures) は本章直結
- Level 2 2-2 セッション: Secure 属性は HTTPS 前提
- Level 3 3-1 サーバーhardening: nginx で TLS を強制する設定
実害シーン: 通信路を守らないとどうなるか
- Firesheep (2010): ブラウザ拡張1つで、同じカフェの全員の Facebook セッションが奪える。これが HTTPS 必須化の起爆剤
- 某航空会社の機内Wi-Fi (継続事案): HTTP のページに広告 JS が挿入され、認証情報窃取の足がかりにされる
- 公衆Wi-Fi MITM: 「Free Airport WiFi」を名乗る偽APが、ログイン情報を全部キャプチャ。被害は今も報告されている
「うちは社内ツールだから HTTP でいい」は 退職者・委託先・侵入された LAN を考慮していない発想。社内こそ HTTPS。
大前提: なぜ HTTPS が必須なのか
ブラウザで http:// のサイトを開くと「保護されていない通信」と警告が出る。あれは脅し文句ではない:
- 盗聴: 同じWi-Fi(カフェ、空港)にいる人があなたの通信を全部読める
- 改ざん: ISP・公衆Wi-Fi・国家ファイアウォールが通信内容を書き換えられる(広告挿入、悪意あるJS埋め込み)
- なりすまし: 「これは本物の
example.comですよ」を保証する仕組みが無い
HTTPS(=HTTP + TLS)はこの3つを同時に解決する。
実際に起きた事件:
- Firesheep (2010): カフェのWi-Fiで他人のFacebookセッションが見え放題だった事件(当時のFacebookはHTTPS必須でなかった)。これでHTTPS化の流れが加速
- 中国の Great Cannon (2015): ISPレベルで広告タグを書き換え、GitHubへのDDoS攻撃に流用された。HTTPS化していたサービスは影響を受けなかった
- 公衆Wi-Fi での個人情報窃取: 「Free Wi-Fi」を名乗る悪意あるアクセスポイントが今も多数存在
2026年現在、HTTPS は「やったほうがいい」ではなく「やらなければ違法・恥」のレベル。Let’s Encrypt のおかげで実質無料・自動化可能。やらない理由がない。
セッション①: HTTPS と TLS の基礎(25-30分)
0. 録画&作業ディレクトリ
mkdir -p ~/log ~/learn/security/day03_tls
cd ~/learn/security/day03_tls
script ~/log/security_day03.log1. HTTPS とは何か
HTTP(生のWeb通信)
↓
HTTP over TLS = HTTPS
↓
通信内容が暗号化される
HTTPS の構造
HTTPS = HTTP をそのまま TLS という暗号化トンネルの中に流すだけ。
- HTTP 自体は変わらない(同じ GET / POST / ヘッダー)
- 下のレイヤーが暗号化される
- サーバー側もアプリは HTTP しか喋らず、リバースプロキシ(nginx等)が TLS を終端することが多い
TLS(Transport Layer Security): 「SSL」と呼ばれていた古い名前を引き継いだ後継規格。
- SSL 1.0/2.0/3.0 → TLS 1.0 → TLS 1.1 → TLS 1.2 → TLS 1.3(2018-)
- 2026年現在はTLS 1.2 と 1.3 のみが現役。TLS 1.0/1.1 はすでに非推奨
2. TLS が解決する3つの脅威
TLSが保証する3つの性質
性質 解決する脅威 技術的手段 機密性(Confidentiality) 盗聴 対称暗号化(AES等) 完全性(Integrity) 改ざん MAC / AEAD 認証(Authentication) なりすまし 証明書 + 公開鍵暗号 この3点セットを CIA と呼ぶ(セキュリティの基本概念)。情報セキュリティの定義そのもの。
3. TLSハンドシェイクの概要
[クライアント] [サーバー]
│ │
│ ── ClientHello ──→ │ 「対応する暗号スイート教えて」
│ (TLS版本、暗号候補、乱数) │
│ │
│ ←── ServerHello ───── │ 「これ使うね、証明書はこれ」
│ (選んだ暗号、証明書、乱数) │
│ │
│ 証明書を検証 │
│ (CAの署名で本物か確認) │
│ │
│ ── 鍵交換 ────────→ │ 公開鍵暗号で対称鍵を共有
│ ←─ 鍵交換 ──────── │ (ECDHE で前方秘匿性)
│ │
│ 対称鍵で暗号化通信開始 ←─────────→ │
ハンドシェイクの本質
「公開鍵暗号で 対称鍵を安全に共有 → その後は対称暗号で高速通信」
なぜ全部を公開鍵でやらないのか:
- 公開鍵暗号(RSA、ECDSA)は 非常に遅い(対称暗号の数百倍)
- 大きなデータ転送に向かない
対策:
- 「鍵共有だけ公開鍵」「実データは対称鍵」のハイブリッド
- 対称鍵は セッションごとに生成・破棄(一度漏れても他セッションは安全 = 前方秘匿性 / Forward Secrecy)
TLS 1.3 で何が変わったか
- ハンドシェイクが 1往復(1-RTT) に短縮(1.2では2往復)
- 0-RTT 再開モード(過去に通信したサーバーへの再接続が高速化)
- 古い暗号アルゴリズム(RC4、3DES、SHA-1)を全廃
- デフォルトで Forward Secrecy 必須
2026年新規構築なら TLS 1.3 だけ有効化するのが理想。クライアント互換性で 1.2 も残すのが現実解。
4. 公開鍵暗号の超ざっくり理解
公開鍵暗号の魔法
鍵を2つ作る:「公開鍵」と「秘密鍵」。
- 公開鍵で暗号化したものは、対応する 秘密鍵でしか復号できない
- 秘密鍵で署名したものは、対応する 公開鍵で検証できる
これにより:
- 暗号化: 「相手の公開鍵で暗号化」→ 相手しか開けない(鍵を事前共有する必要がない)
- 署名: 「自分の秘密鍵で署名」→ 受け取った人は公開鍵で検証 → 「確かにあなたが書いた」と判明
アルゴリズム例: RSA(古典)、ECDSA / Ed25519(楕円曲線、現代的)
TLSではこれを「対称鍵の共有」と「サーバー身元の証明」に使う。
5. 証明書チェーンと認証局(CA)
ルート証明書(DigiCert Root CA) ← OSやブラウザに最初から入っている
│ (秘密鍵で署名)
↓
中間証明書(DigiCert Intermediate)
│ (秘密鍵で署名)
↓
サーバー証明書(example.com) ← サーバーが提示する
証明書チェーンとは
サーバーが「私は example.com です」と自称するだけでは信用できない。第三者の 認証局(CA: Certificate Authority) に証明書を発行してもらう。
CAは厳しい審査の末、「このドメインは確かにこの組織のもの」と署名してくれる。
ブラウザは:
- サーバー証明書を取得
- その証明書が中間CAで署名されている → 中間CA証明書を確認
- 中間CAがルートCAで署名 → ルートCAは OS/ブラウザに事前インストールされている ので信頼OK
このチェーンが切れていたり、ルートCAが信頼リストに無いと「証明書エラー」になる。
認証局(CA)の例
- Let’s Encrypt: 無料・自動。世界中の半分以上のWebサイトが利用
- DigiCert / GlobalSign / Sectigo: 有償の老舗。EV証明書(緑バー)など
- Cloudflare: CDN経由で証明書も提供
CAは「ドメインの本人確認」だけする(DV: Domain Validation)。法人の実在まで確認するEV(Extended Validation)は高い。現代では DV で十分という風潮。
6. Let’s Encrypt と ACME プロトコル
Let's Encrypt の革命
2015年開始、無料、自動更新可能なCA。事実上、Webから「証明書取得の障壁」を消した。
仕組み:
- ACME(Automated Certificate Management Environment)プロトコルでCAと通信
- CA「あなたが本当に example.com の所有者なら、
http://example.com/.well-known/acme-challenge/xxxに特定の文字列を置いて」(HTTP-01 チャレンジ)- クライアントが指定された場所にファイル配置
- CAがアクセスして確認
- OKなら証明書発行
これを 証明書発行のたびに自動実行。クライアントツール「certbot」が定番。
ユースケース: 個人ブログから大企業のサービスまで。ほぼ全Webサービスのデフォルト選択肢 落とし穴:
- 有効期限は 90日 と短い。自動更新スクリプト必須(cronで動かす)
- レート制限あり(同一ドメインで週20件など)
- ワイルドカード証明書はDNS-01 チャレンジ(DNSレコードで認証)が必要
7. 実際に証明書を覗いてみる
# Google の証明書を取得して表示
openssl s_client -connect google.com:443 -servername google.com </dev/null 2>/dev/null \
| openssl x509 -noout -text
# 重要情報だけ取り出す
openssl s_client -connect google.com:443 -servername google.com </dev/null 2>/dev/null \
| openssl x509 -noout -subject -issuer -dates
# ファイルに保存して観察
openssl s_client -connect google.com:443 -servername google.com </dev/null 2>/dev/null \
| openssl x509 > ~/learn/security/day03_tls/google.crt観察ポイント
Subject: 誰のための証明書か(CN = Common Name、SAN = Subject Alternative Name に複数ドメイン)Issuer: 誰が署名したか(CA名)Not Before / Not After: 有効期間Signature Algorithm: 署名アルゴリズム(sha256WithRSA、ecdsa-with-SHA256 など)Public Key: 公開鍵情報
ブラウザでも確認できる
アドレスバーの鍵マーク → 「証明書」で同じ情報が見られる。社内サイトや本番デバッグで重宝。
セッション②: 攻撃と対策、開発時の落とし穴(25-30分)
8. MITM(Man-in-the-Middle / 中間者)攻撃
[本物のクライアント] ←→ [攻撃者] ←→ [本物のサーバー]
↑ 通信を中継しながら盗聴・改ざん
MITM 攻撃のシナリオ
- 公衆 Wi-Fi で偽アクセスポイントを立てる
- ユーザーがそこに繋ぐ
- 攻撃者がユーザーとサーバー両方と別々に通信を確立
- 通過する通信を読み放題、書き換え放題
HTTPSが防ぐ仕組み:
- サーバー証明書をCAで検証 → 攻撃者は本物の証明書を持っていないので、ブラウザがエラーを出す
- 暗号化されているので、中身が見えても読めない
HTTPSが破られるパターン:
- ユーザーが証明書エラーを無視して「進む」を押す
- 攻撃者が偽CAをユーザーのPCに事前インストール(社内ネット監視用ProxyとかMDM経由とか)
- サーバー側で HTTP のままレスポンスしてしまうエンドポイントがある(HSTSで防ぐ)
9. HSTS(HTTP Strict Transport Security)
Strict-Transport-Security: max-age=31536000; includeSubDomains; preloadHSTS の本質
サーバーが「このドメインには次から1年間、HTTPSでしかアクセスするな」とブラウザに命令するHTTPヘッダー。
何を防ぐか:
- SSL Strip 攻撃: ユーザーが
example.comと打つ → ブラウザは最初HTTPでアクセス → 攻撃者がここをHTTPで奪う、というシナリオを潰す- ユーザーが手で
http://と打っても、ブラウザが内部的にhttps://に書き換えてアクセスオプション:
max-age: 何秒間この設定を覚えておくか(1年 = 31536000 が標準)includeSubDomains: サブドメインも含めて強制preload: HSTS Preload List に登録されると、初回アクセスから HTTPS 強制 される落とし穴: 一度有効にすると
max-ageが切れるまで戻せない。プレリロードに登録すると、削除には数ヶ月かかる。本番投入前に開発環境で十分テスト
10. Certificate Pinning(証明書ピンニング)
Pinning の本質
アプリ(特にモバイル)が「このサーバーが提示する証明書はこれと一致してないと拒否」というロジックを アプリ内に焼き込む。
何を防ぐか:
- 不正なCAが攻撃者用に発行した偽証明書(CA自体が攻撃された場合)
- MDM経由でユーザー端末に偽CAを仕込まれている場合
ユースケース: 銀行アプリ、決済アプリ、メッセンジャー(Signal等) 落とし穴:
- 証明書を更新するたびにアプリ更新が必要 → 運用負荷大
- ミスると アプリ全停止(証明書更新したらユーザー全員が繋がらない)
- 一般的なWebサービスでは過剰、モバイルアプリでも採用は慎重に
- 2017年頃のPKP(HTTP Public Key Pinning)はブラウザではすでに非推奨
11. 開発時の落とし穴
自己署名証明書(self-signed cert)
# 自己署名証明書を作る(開発用)
openssl req -x509 -newkey rsa:2048 -keyout key.pem -out cert.pem -days 365 -nodes \
-subj "/CN=localhost"自己署名証明書の罠
- CAが署名していない → ブラウザは「危険」と警告
- 開発時の許容方法:
- ブラウザで「詳細設定 → 進む」(自分のローカルでだけ)
- 開発マシンの信頼ストアに証明書を追加
- mkcert ツールを使うとローカル開発用CAを楽に作れる(推奨)
- 本番に絶対持ち込まない: お客様に証明書警告を出すサービスは終わってる
# mkcert を使ったほうが楽
brew install mkcert
mkcert -install
mkcert localhost 127.0.0.1
# → localhost-key.pem / localhost.pem が生成され、ローカルで信頼されるlocalhost と HTTPS
localhost は実は特別扱い
- ブラウザは
http://localhostを「安全な文脈(secure context)」とみなす- これは Service Worker、WebCrypto、HTTPS 必須の API を localhost でも使えるようにするため
127.0.0.1は別扱い だったが、現代ブラウザでは localhost と同等に扱われるただし「同じネットワーク上の他端末からアクセスする」場合(モバイル実機テスト)は localhost ではないので、自己署名 or mkcert が必要
混在コンテンツ(Mixed Content)
<!-- HTTPS ページの中で HTTP のリソースを読み込む -->
<script src="http://example.com/old.js"></script>
<!-- ↑ これでブラウザが「混在コンテンツ」と警告 -->Mixed Content の問題
HTTPSページ内で HTTP リソースを読み込むと、その HTTP リソースは MITM 可能 = ページ全体の安全が破綻する。
ブラウザの対応:
- Active content(script、iframe、CSS): ブロックされる
- Passive content(img、video): 警告のみ(または自動 https へ昇格)
対策: 全リソースを HTTPS で読み込む。
//cdn.example.com/lib.jsのような protocol-relative URL はもう使わず、明示的にhttps://を書く
ローカル開発でHTTPSにする方法
# 1. mkcert で証明書生成
mkcert localhost
# 2. Node.js / Goのサーバーで起動
# Node:
# https.createServer({key, cert}, app).listen(443)
# 3. Caddy を使うとさらに楽(ローカルもLet's Encryptも自動)
brew install caddy
echo "localhost {
reverse_proxy localhost:3000
}" > Caddyfile
caddy runCaddy で開発もHTTPS
Caddy は ローカルなら mkcert 自動、本番なら Let’s Encrypt 自動 で HTTPS化してくれる。設定ファイルが極小で済む。最近の推し。
12. 暗号スイートと現代の選択
暗号スイートとは
TLSハンドシェイクで「これ使って通信しましょう」と合意する アルゴリズムの組合せ:
TLS_AES_256_GCM_SHA384 │ │ │ │ │ └─ メッセージ認証用ハッシュ │ └───── 対称暗号モード(GCMが現代の主流) └─────────── 対称暗号アルゴリズム2026年現在の推奨:
- TLS 1.3 がサポートするスイートはすべて安全(古いのを切り捨てた設計)
- TLS 1.2 では
ECDHE系(前方秘匿性あり)+AES-GCM+SHA-256/384を選ぶ- RC4、3DES、CBC モード、SHA-1 などは無効化
設定支援: Mozilla の SSL Configuration Generator が便利。nginx/Apache の設定をクリックで生成
13. nginx での TLS 設定の最小例
server {
listen 443 ssl http2;
server_name example.com;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
# 現代的な設定(Mozilla Intermediate)
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers off;
ssl_session_cache shared:SSL:10m;
ssl_session_tickets off;
# HSTS(HTTPS 必須化)
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
location / {
proxy_pass http://localhost:3000;
}
}
# HTTP は HTTPS にリダイレクト
server {
listen 80;
server_name example.com;
return 301 https://$host$request_uri;
}このnginx設定で起きていること
- TLS 1.2/1.3 のみ受け付け
- HSTS で「次回からHTTPS強制」をブラウザに指示
- HTTPで来た接続を301でHTTPSに転送
- 内部の Node/Go アプリは普通の HTTP で動かす(TLS終端は nginx 担当)
練習課題
# 1. 自分が普段使うサービスの証明書を3つ覗く
for domain in google.com github.com github.io; do
echo "=== $domain ==="
openssl s_client -connect $domain:443 -servername $domain </dev/null 2>/dev/null \
| openssl x509 -noout -subject -issuer -dates
echo ""
done > ~/learn/security/day03_tls/certs.txt
# 2. ローカル開発用に mkcert を入れる
brew install mkcert
mkcert -install
mkcert localhost
# 3. Goで簡単なHTTPSサーバーを立ててブラウザで開く
cat > server.go << 'EOF'
package main
import (
"fmt"
"log"
"net/http"
)
func main() {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "Hello over HTTPS!")
})
log.Println("Listening on https://localhost:8443")
log.Fatal(http.ListenAndServeTLS(":8443", "localhost.pem", "localhost-key.pem", nil))
}
EOF
go run server.go
# 別ターミナルで: curl https://localhost:8443締め: 振り返り(10分)
1. セッション録画を終了
exit2. 今日の発見
- TLSハンドシェイクで「対称鍵を共有する」が腑に落ちたか:
- 自分のサービスは HSTS を入れているか:
- 自己署名証明書を本番で使っている場面に出会ったら何と言うか:
- 明日やりたいこと:
チェックリスト
- HTTPSの内部構造(HTTP + TLS)が言える
- TLSが保証する3つの性質(機密性・完全性・認証)を言える
- TLSハンドシェイクで「公開鍵→対称鍵に乗り換える」流れが言える
- 証明書チェーン(ルート → 中間 → サーバー)を絵で書ける
- Let’s Encrypt と ACME を1分で説明できる
- HSTS が何を防ぐか言える
-
openssl s_clientでサイトの証明書を覗いた - mkcert でローカルHTTPS環境を作った
詰まった時のチートシート
| やりたいこと | コマンド/設定 |
|---|---|
| サイトの証明書を見る | openssl s_client -connect host:443 -servername host |
| 証明書の有効期限を確認 | `… |
| 証明書をファイル化 | `… |
| ローカル用HTTPS | mkcert localhost |
| HSTSヘッダー | Strict-Transport-Security: max-age=31536000; includeSubDomains |
| HTTP→HTTPSリダイレクト(nginx) | return 301 https://$host$request_uri; |
| Let’s Encrypt取得 | certbot --nginx -d example.com |
| TLS 1.3のみ有効化(nginx) | ssl_protocols TLSv1.3; |
| HTTPS版図解 | HTTP の中身が暗号化される、それだけ |
「実務OK」基準
- HTTPS必須を当然と思える: 「うちは社内だからHTTP」とか言わない
openssl s_clientでサーバーの証明書を即座に確認できる- mkcert などでローカルHTTPS環境を構築できる
- 混在コンテンツ警告に遭遇したら原因をすぐ突き止められる
- HSTSのリスクと利点を両方説明できる
メタ認知: 自分の理解度を診断する
30秒セルフテスト
- TLS ハンドシェイクで「鍵交換」と「認証」はそれぞれ何で行われる?
- CA・中間証明書・サーバー証明書の チェーン が必要な理由を1文で
- HSTS が「設定したら戻せない」と言われる理由は?
- Let’s Encrypt の証明書有効期限は何日?
openssl s_client -connect example.com:443で何が分かる?詰まったら → セッション①のハンドシェイク図を再描画してみる。手で描くと記憶定着率が跳ねる。
次のレッスン
1-4 OWASP Top 10 でWebアプリ脆弱性の代表選手10種を概観する。HTTPSで通信路は守れても、アプリ自体に穴があれば意味がない。攻撃の世界を網羅的に覗く回。
間隔反復ポイント
TLS の細部は 半年も触らないと忘れる。3ヶ月後にこのページの「詰まった時のチートシート」を眺め直すと、
certbotコマンドや HSTS 設定が即座に出てくる状態を維持できる。