TLS (Transport Layer Security) は、TCP の上に挟まる薄い暗号通信層。HTTPS の "S"、つまり URL の https:// や鍵マークの正体はこれ。アプリケーションプロトコル (HTTP / SMTP / IMAP ...) から見れば「ただの暗号化されたバイトストリーム」として透過的に動くので、HTTP を一切改造せずに HTTPS が実現できた。本稿は 役割 → ハンドシェイク → 証明書と PKI → 暗号アルゴリズム → openssl での診断 → 過去の脆弱性 → 運用の現実解 という流れで、サーバ運用とトラブルシュートに必要な範囲だけを扱う。
難しく見えても、本質は次の 3 つだけ持ち帰ればいい。(1) TLS は ブラウザの鍵マーク 🔒 の中身で、HTTPS の "S" がこれ。(2) 仕事は 「盗聴されない / 改ざんされない / 相手が本物だと分かる」の 3 つ。(3) 通信を始める前に「ハンドシェイク」と呼ばれる挨拶で 合言葉 (= 鍵) を決め、相手の身分証 (= 証明書) を確認してから本文を送る。— ここを土台に各章を順に開いていけば、迷子になりにくい。
TLS が提供する 3 つの保証 #
TLS が提供するのは、シンプルに次の 3 つだけ。
- 機密性 (Confidentiality) — 通信内容を盗聴されても読めない (AES-GCM / ChaCha20-Poly1305)
- 完全性 (Integrity) — 通信内容が改ざんされたら検出できる (AEAD の認証タグ)
- 認証 (Authentication) — 通信相手が「本物」であることを保証 (X.509 証明書 + PKI)
3 つ目の 認証 が一番大事で、これがなければ前 2 つは無意味。攻撃者が中間者になっていれば「攻撃者と暗号化通信して終わり」だからだ。curl -k や --insecure はこの保証を捨てる操作で、本番では絶対に使わない。
普通のはがき (HTTP) は配達員にも近所の人にも読まれるし、書き換えても誰にもバレない。TLS はこれを (1) 封筒に入れて中を読めなくし (機密性)、(2) 封印シールで開封の有無が分かるようにし (完全性)、(3) 差出人が名乗っている本人だと身分証で確認する (認証) — の 3 段構えで守る。curl -k は「相手の身分証を見ずに荷物を渡す」操作、つまり 暗号化はしても相手が偽物かもしれない状態。本番では絶対に使わない。
歴史的経緯で「SSL」と「TLS」が混在して呼ばれるが、現代運用されているのはすべて TLS。Netscape の SSL 2.0 (1995) / 3.0 (1996) を IETF が標準化して TLS 1.0 (1999) → 1.1 (2006) → 1.2 (2008) → 1.3 (2018, RFC 8446) と進化、2026 年現在の HTTPS は 95% 以上が TLS 1.2 か 1.3。それでも「SSL 証明書」「SSL 化」といった慣用表現は商業文書に残るので、両方に反応できればよい。
TLS が挟まる位置は Application 層と Transport 層の間。HTTP は TCP の上に乗るが、HTTPS は HTTP → TLS → TCP の 3 層構造になり、TLS が暗号化・復号して上下の層に透過させる。HTTP/3 では TLS 1.3 が QUIC (UDP の上の独自トランスポート) に組み込まれている。
ハンドシェイク — 1 往復で鍵を作る #
ハンドシェイクは TLS の心臓部で、鍵交換と相手認証を完了させ、以降の通信に使う セッション鍵 を確立する。TLS 1.3 では 1-RTT (1 往復) で完了し、TLS 1.2 の半分。
外国の人と話し始める前に 「どの言語で話すか」「合言葉は何か」「相手は本物か」を一気に決めるのがハンドシェイク。ClientHello が「英語と日本語どっちで話せます?」、ServerHello が「英語で。あと身分証はこれです」、Finished が「OK、じゃあ本題に入りましょう」。TLS 1.3 ではこのあいさつが 1 往復 (1-RTT) で終わるので、ページ表示が体感で速くなった、というのが実用上のメリット。
application_data が対称暗号で流れるだけ。実際に 1 ステップずつ動かす — ClientHello から Finished まで、メッセージがクライアント⇄サーバ間をどう往復して暗号化が確立するかを追ってみよう。
ポイントは「ClientHello に key_share を載せて出す」こと。クライアントが見切り発車で ECDHE の公開鍵を投げるので、サーバの 1 往復目で鍵共有が成立する。TLS 1.2 は ServerHello を待ってから ClientKeyExchange を投げる 2-RTT 構造だった。
# TLS 1.2 — 証明書は平文、2 往復
Client Server
| ClientHello |
|------------------------------------>|
| ServerHello / Certificate / KeyEx | ← 平文で流れる
|<------------------------------------|
| ClientKeyExchange / Finished |
|------------------------------------>|
| Finished |
|<------------------------------------|
|===== Application Data (暗号化) =====|
# TLS 1.3 — 証明書も暗号化、1 往復
Client Server
| ClientHello (+ key_share) |
|------------------------------------>|
| ServerHello + {Cert, Verify, Fin} | ← {} は暗号化済
|<------------------------------------|
| Finished |
|------------------------------------>|
|===== Application Data (暗号化) =====|過去にハンドシェイク済みのサーバなら、再接続時に PSK (Pre-Shared Key) を使って 0-RTT で application_data を最初の往復に乗せることができる。スマホアプリで毎回ハンドシェイクの遅延を払いたくないユースケースで効く。ただし 0-RTT データはリプレイ可能性があるため、副作用のない idempotent な GET 等に限定する。決済処理を 0-RTT に乗せてはいけない。
証明書と PKI — 信頼の連鎖 #
海外の入国審査で見せるのがパスポート、Web サイトが「自分は本物の hackinglabo.com だ」と示すのが サーバ証明書。パスポートを発行する政府が CA (認証局)、その大元の信頼の根が Root CA、実発行を担当する出先機関が Intermediate CA。ブラウザは「サーバ証明書 → 中間 CA → Root CA」と辿り、最後の Root が OS / ブラウザに最初から登録されている信頼リストに入っていれば「本物」と判断する。Let's Encrypt はこのパスポートを 無料で機械的に発行してくれる仕組みで、これのおかげで個人サイトでも HTTPS 化が当たり前になった。
サーバ証明書は X.509 形式で、次のような情報の塊。
- Subject —
CN=hackinglabo.comと SAN (Subject Alternative Name) に対象ドメインを列挙 - Issuer — どの CA が署名したか
- Validity — 有効期間 (パブリック CA は最大 398 日)
- Public Key — このドメインの公開鍵 (RSA / ECDSA / Ed25519)
- Extensions — Key Usage / EKU / AIA / CRL DP / SCT (Certificate Transparency)
- Signature — 上記全フィールドに対する CA の署名
実サーバ証明書は単独で立たず、CA の署名で 「Root CA → Intermediate CA → Server Cert」 という証明書チェーンの末端にある。OS / ブラウザのトラストストアに登録された Root CA まで遡れて初めて「信頼」が成立する。
Root CA ISRG Root X1 # OS/ブラウザにバンドル、自己署名
│ 署名
Intermediate R3 / R10 / E5 # 実発行はこの中間 CA から
│ 署名
Server Cert CN=hackinglabo.com # 配信される証明書
# クライアントは Server → Intermediate → Root と辿り、Root が信頼済なら OKLet's Encrypt と ACME による自動化 #
2015 年開始の Let's Encrypt は無料・自動化で証明書を発行する CA で、現在世界の HTTPS の 半分以上 を占める。プロトコルは ACME (RFC 8555)、certbot / acme.sh などがドメイン所有確認 (HTTP-01 / DNS-01 / TLS-ALPN-01) を経て 90 日有効 の証明書を自動で取得・更新する。
| チャレンジ | 仕組み | 向くケース |
|---|---|---|
| HTTP-01 | http://example.com/.well-known/acme-challenge/... にトークンを置く |
一般的な Web サーバ |
| DNS-01 | _acme-challenge.example.com の TXT レコードにトークンを書く |
ワイルドカード証明書、内部サーバ |
| TLS-ALPN-01 | 443 番で特殊な ALPN を返す | HTTP-01 が使えない 443 のみのサービス |
失効確認と Certificate Transparency #
鍵漏洩などで証明書を強制的に無効化する仕組みが失効。現代の主流は、サーバが OCSP 応答を取得してハンドシェイクに同梱する OCSP Stapling と、ブラウザがブルームフィルタで一括配布する CRLite / OneCRL の併用。
不正発行検出のため、CA は発行した全証明書を 公開ログ (Certificate Transparency) に記録することが Chrome から義務化されている (2018 年〜)。証明書には少なくとも 2 つの独立 CT ログ由来の SCT が埋め込まれる。運用者は crt.sh で「自ドメインに対していつ・どこの CA がどんな証明書を出したか」を全部監視できる — 不正発行の早期発見の決め手。
暗号アルゴリズム — Forward Secrecy と AEAD #
TLS 1.2 の暗号スイートは「鍵交換 + 認証 + 対称暗号 + MAC」の 4 要素を 1 文字列で指定する複雑な仕組みだった。TLS 1.3 でこれが大幅に整理され、現代運用で出会う組み合わせは数えるほど。
鍵交換 + 認証 + 対称暗号 + ハッシュ の 4 部品の組み合わせ表が暗号スイート。ラーメン屋でいう「醤油 + 細麺 + 中盛 + 味玉」のセット指定。TLS 1.2 までは選択肢が膨大で運用者が選定ミスをしがちだった反省から、TLS 1.3 では 「弱い具材」を全部メニューから消した。だから TLS 1.3 を有効にして後述の Mozilla Intermediate プロファイルを貼っておけば、暗号スイートの細かい中身は意識しなくてよい設計になっている。Forward Secrecy は「サーバの長期秘密鍵が 将来漏れても、過去に流れた通信ログは復号できない」性質 — 言い換えると 「過去の自分」を未来の事故から守る保険。
TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 # TLS 1.2 形式
│ │ │ │
│ │ │ └─ HKDF / PRF のハッシュ
│ │ └─ 対称暗号 (AEAD)
│ └─ サーバ証明書の鍵タイプ (認証)
└─ 鍵交換 (ECDHE = Forward Secrecy あり)
# TLS 1.3 — 鍵交換と認証は別交渉、対称暗号 + ハッシュだけになる
TLS_AES_128_GCM_SHA256
TLS_AES_256_GCM_SHA384
TLS_CHACHA20_POLY1305_SHA256鍵交換 — Forward Secrecy が分水嶺 #
Forward Secrecy (FS, PFS) とは「長期の秘密鍵が将来漏洩しても、過去の通信は復号できない」性質。これを満たすのは一時鍵を使う DHE / ECDHE だけで、Premaster を RSA で暗号化送信する古典的な「RSA 鍵交換」では満たせない (RSA 秘密鍵が将来漏れたら過去の全通信が復号できる)。TLS 1.3 で RSA 鍵交換が完全削除されたのはこのため。実運用の鍵交換は X25519 か P-256 の ECDHE がほぼすべて。
AEAD で平文と認証を一体化する #
AEAD (Authenticated Encryption with Associated Data) は暗号化と完全性検証を 1 操作で行う方式。「先に MAC かけてから暗号化するか、暗号化してから MAC かけるか」といった padding oracle 攻撃の温床になる順序問題を構造的に消す。TLS 1.3 では AEAD のみが許され、選択肢は実質次の 3 種類。
- AES-128-GCM / AES-256-GCM — CPU の AES-NI で爆速、サーバ向け定番
- ChaCha20-Poly1305 — AES-NI のないモバイル CPU で高速、Google 主導
- AES-CCM — IoT 向けの軽量版
サーバ証明書の署名鍵 #
「サーバ本物」を証明するための鍵タイプは 3 系統。古い順に RSA-2048 / 3072 (互換性最強)、ECDSA P-256 / P-384 (鍵が短く高速)、Ed25519 (TLS 1.3 から標準化、最新)。一般 Web サイトは ECDSA P-256 + RSA-2048 のデュアル証明書 が無難。
- RSA 鍵交換 — Forward Secrecy なし
- CBC モード — BEAST / Lucky13 / POODLE の温床
- 3DES / RC4 — 古典的に弱い対称暗号
- MD5 / SHA-1 — 衝突耐性なし
- 圧縮 — CRIME / BREACH の温床
これらをまとめて切ったので、TLS 1.3 サーバを建てるだけで弱い暗号は構造的に選ばれない。
openssl で TLS を診断する #
TLS が絡む不具合のほぼすべて — 証明書チェーン不備、SNI ミス、古いプロトコルへの fallback、期限切れ — は openssl 1 本で原因が割り出せる。
たくさんあるオプションに圧倒される前に、初手はこれだけでよい:echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null | openssl x509 -noout -subject -issuer -dates
これで 「誰のサイトか / どの CA が発行したか / 有効期限はいつまでか」の 3 点が一発で見える。証明書まわりの調査の 9 割はこの 1 本で片付く。深掘りしたくなったら、本章後半の testssl.sh や Qualys SSL Labs に進めばいい。
# 接続して証明書チェーン + ハンドシェイクを観察
$ openssl s_client -connect hackinglabo.com:443 -servername hackinglabo.com
# プロトコルバージョン指定 (互換性テスト)
$ openssl s_client -connect example.com:443 -tls1_3
$ openssl s_client -connect example.com:443 -tls1_2
# 証明書を全部 PEM で取り出す (チェーン検証に使う)
$ openssl s_client -connect example.com:443 -showcerts < /dev/null# サーバから取って直接表示 (期限・SAN・発行元の確認に最も使う)
$ echo | openssl s_client -connect example.com:443 2>/dev/null \
| openssl x509 -noout -subject -issuer -dates -ext subjectAltName
# 証明書ファイルから読む
$ openssl x509 -in cert.pem -noout -text
# SHA-256 フィンガープリント (HPKP / DANE で使う)
$ openssl x509 -in cert.pem -noout -fingerprint -sha256# ECDSA P-256 鍵を生成
$ openssl ecparam -genkey -name prime256v1 -out key.pem
# 自己署名証明書 (1 年有効) を 1 行で
$ openssl req -x509 -key key.pem -out cert.pem -days 365 \
-subj "/CN=test.example.com"# curl — TLS ハンドシェイクログを詳細に
$ curl -v --tls-max 1.3 https://example.com
# nmap — 対応暗号スイート列挙、Heartbleed チェック
$ nmap --script ssl-enum-ciphers -p 443 hackinglabo.com
$ nmap --script ssl-heartbleed -p 443 example.com
# testssl.sh — 設定ミス・既知脆弱性を 1 発で全部洗う定番ツール
$ ./testssl.sh https://hackinglabo.com
$ ./testssl.sh -U https://hackinglabo.com # 脆弱性のみオンライン版なら Qualys SSL Labs が定番で、A+ / A / B のグレード評価がもらえる。本番公開前に 1 回は通したい。
過去の脆弱性が教えてくれること #
TLS の過去の脆弱性史は「現代の設定で何を捨てるべきか」をそのまま示している。代表だけ押さえれば、運用設定の意味が腑に落ちる。
過去の TLS 脆弱性は要するに 「過去にこんな失敗があったから、現代は これを使ってはいけない」という生きた教訓集。BEAST / POODLE → CBC は捨てる、CRIME → TLS 圧縮を切る、FREAK / Logjam → 短い RSA / DH を切る、Heartbleed → OpenSSL は常に最新に保つ。下の表を一つひとつ暗記する必要はなく、TLS 1.3 + Mozilla Intermediate プロファイルを選ぶだけで、これらの「危ない選択肢」は構造的に消える、という事実だけ最初に押さえておけばよい。
| 年 | 攻撃名 | 何が起きた | 教訓 |
|---|---|---|---|
| 2011 | BEAST | TLS 1.0 / CBC で平文を 1 byte ずつ復元 | CBC は捨てる、AEAD に移行 |
| 2012 | CRIME / BREACH | TLS / HTTP 圧縮を悪用して Cookie をリーク | TLS 圧縮は無効化 |
| 2013 | Lucky 13 | CBC + HMAC の処理時間差で padding 推測 | AEAD への完全移行 |
| 2014 | Heartbleed | OpenSSL の Heartbeat 拡張のバウンドチェック忘れで、サーバメモリ最大 64 KB がリーク。秘密鍵まで漏れうる (CVE-2014-0160) | 全証明書再発行 + パッチ運用 |
| 2014 | POODLE | SSL 3.0 の CBC パディング検証の甘さで平文復元 | SSL 3.0 完全無効化 (RFC 7568) |
| 2015 | FREAK / Logjam | 90 年代の輸出規制で残った 512 bit RSA / DH をダウングレード悪用 | EXPORT 暗号削除、DH を 2048 bit 以上 |
| 2016 | DROWN | 同じ秘密鍵を SSL 2.0 と TLS で共用 → 復号 | SSL 2.0 を世界中から排除 |
| 2017 | ROBOT | 1998 年の RSA Bleichenbacher 攻撃の再来 | ECDHE / TLS 1.3 移行 |
OpenSSL の Heartbeat 拡張の境界チェック忘れというたった 1 つのバグで、世界中のサーバの秘密鍵が漏洩しうる状態になった。Yahoo!・Google・AWS が同時に対応した過去最悪級の TLS 脆弱性で、「暗号は数学だが、実装は人間が書く」ことを思い出させる事件。openssl version を時々確認し、CVE が出たら即パッチ、というオペレーションが現代運用の基本になった。
MITM (中間者攻撃) と現代の防御 #
不正な CA 発行 (DigiNotar 2011 / Symantec 2017) や、企業・家庭ルータの「TLS インスペクション」での証明書差し替えなど、中間者は今も現実の脅威。対策は次の組み合わせ。
- HSTS — 一度 HTTPS で接続したサイトは平文 HTTP を拒否、
hstspreload.orgでブラウザに事前登録 - Certificate Transparency — 不正発行を CT ログで可視化、
crt.shで監視 - DANE / TLSA — DNSSEC で証明書を縛る (運用ハードルは高い)
- CRLite / OneCRL — ブラウザ側で失効を一括配布
運用の現実解 #
ここまでの内容を踏まえると、実運用で押さえるべきは「プロファイル選び」と「勝手に古びさせない仕組み」の 2 点に集約される。
個人ブログや小規模 Web サイトで TLS をきちんと整えたいなら、まずこの 4 つを揃えればよい。(1) Let's Encrypt + certbot で証明書を取得。(2) Mozilla の Intermediate プロファイルを nginx / Apache 設定にそのまま貼る。(3) HTTP → HTTPS の 301 リダイレクト + HSTS で平文経路を構造的に閉じる。(4) cron で証明書自動更新。仕上げに「年に 1 回、Qualys SSL Labs で A+ が出ているか」を確認する習慣だけ付けておけば、運用として十分に堅い。
Mozilla のテンプレートを使う #
ハンドシェイクと暗号スイートの組み合わせを自前で考える必要はない。Mozilla SSL Configuration Generator (ssl-config.mozilla.org) が 3 プロファイルをコピペで提供する。これが事実上の業界標準。
| プロファイル | 対応 TLS | 想定読者 |
|---|---|---|
| Modern | 1.3 のみ | API / 社内系で「最新ブラウザだけ」で済むとき |
| Intermediate | 1.2 + 1.3 | 一般 Web サイト。迷ったらこれ |
| Old | 1.0〜1.3 | Windows XP / Android 4 への互換が法的に必要なときのみ |
仕組みで守る運用ループ #
設定を 1 度入れたら終わりにせず、自動更新と定期監視で「気付ける」状態を作る。事故の大半は人の手動オペレーションを残したことに起因する。
- certbot / acme.sh + cron で証明書を自動更新 — 手動更新を残すと期限切れ事故は必ずいつか起こる
- OCSP Stapling を有効化 — 失効確認の遅延とプライバシー漏洩を同時に解消
- HTTP → HTTPS 301 + HSTS preload 登録 (hstspreload.org) — 平文 HTTP を構造的に閉じる
- testssl.sh / Qualys SSL Labs を定期実行 — 年 2 回 + 重大 CVE 公表時に走らせる
- crt.sh で CT 監視 — 自ドメインへの不正発行を早期発見
- OpenSSL を定期更新 — Heartbleed 級の CVE は今後も出る前提でパッチ運用
SNI を暗号化する ECH (Encrypted Client Hello) や、量子コンピュータ耐性のある PQ 暗号 (X25519MLKEM768 など) は 2026 年時点でブラウザと CDN で段階導入が始まったところ。情報を追っておく価値はあるが、本番投入を急ぐより、まずは TLS 1.3 + Intermediate プロファイル + 自動更新の土台を盤石にする方が利得が大きい。