HTTPSとTLSの仕組みを徹底解説:ハンドシェイクの全手順
URLの前にhttps://を入力し、鍵アイコンを見て、そのまま進んだことはありませんか?しかし、深夜2時に証明書が期限切れになったり、混在コンテンツの警告でチェックアウトページが壊れたり、クライアントから「API呼び出しがTLSエラーで失敗する理由は?」と聞かれたりすると、「鍵アイコンがあれば安全」という答えは役に立ちません。この記事では、HTTPS接続中に実際に何が起きているのか、そして推測に頼らずに設定する方法を説明します。
HTTPSがHTTPに追加するもの
HTTPSは、TLS(Transport Layer Security)セッション内で運ばれる単なるHTTPです。TLSは3つの具体的な保証を提供します:
- 機密性 — トラフィックは暗号化され、中間者は読めません。
- 完全性 — 転送中の改ざんは検出され、接続は失敗します。
- 認証 — サーバーは提示した証明書の秘密鍵を制御していることを証明します。
このリストにないものに注意してください:HTTPSはサーバーが信頼できるか、アプリケーションに脆弱性があるか、保存データが安全かについては何も言いません。保護するのはパイプであり、エンドポイントではありません。
TLSハンドシェイクの手順
ハンドシェイクは、HTTPバイトが流れる前に行われるネゴシエーションです。TLS 1.3では1往復、TLS 1.2では2往復かかります。以下はTLS 1.3の流れを平易に説明したものです:
- ClientHello — クライアントはサポートするTLSバージョン、暗号スイートのリスト、ランダムなノンス、およびDiffie-Hellman鍵交換(通常はX25519)の鍵シェアを送信します。
- ServerHello — サーバーはバージョンと暗号スイートを選択し、自身の鍵シェアを返し、証明書チェーンとハンドシェイクトランスクリプトに対する署名を送信します。
- 鍵導出 — 双方がDiffie-Hellman交換から共有秘密を計算します。エフェメラル鍵によりセッション鍵は一意になり、前方秘匿性が保たれます:後でサーバーの長期鍵が漏洩しても、記録されたトラフィックは復号できません。
- Finished — 双方がトランスクリプトに対するMACを送信します。何かが改ざんされていれば、ハンドシェイクは中断されます。
- アプリケーションデータ — HTTPリクエストとレスポンスは、共有秘密から導出された対称鍵で暗号化されます。
認証は証明書が担います。サーバーは秘密鍵でハンドシェイクに署名し、クライアントは証明書内の公開鍵を使ってその署名を検証し、さらに信頼されたルートCAに対して証明書自体を検証し、ホスト名、有効期間、失効ステータスを確認します。
対称暗号と非対称暗号:両方が使われる理由
非対称暗号(RSAまたはECDSA)は遅いですが、鍵配送の問題を解決します。対称暗号(AES-GCM、ChaCha20-Poly1305)は高速ですが、共有鍵が必要です。TLSはハンドシェイク中にのみ非対称暗号を使用して対称セッション鍵を確立し、その後はその鍵で大量のデータを暗号化します。これが、最新のTLS 1.3接続が高速な理由です:1往復の後、残りは安価なAESまたはChaCha20で処理されます。
暗号スイートと実際に重要なこと
暗号スイートは、アルゴリズムの名前付きバンドルです。TLS 1.3ではリストは短く、すべてのオプションが安全と見なされます。TLS 1.2では慎重に選択する必要があります。簡単な比較:
| 側面 | TLS 1.2 | TLS 1.3 |
|---|---|---|
| ハンドシェイクの往復回数 | 2(または再開で1) | 1(再開で0-RTT) |
| 鍵交換 | RSAまたは(EC)DHE | エフェメラル(EC)DHEのみ |
| 前方秘匿性 | オプション | 常に |
| レガシー暗号(CBC、RC4) | 設定ミスで可能 | 削除済み |
実用的なポイント:TLS 1.2と1.3のみを有効にし、TLS 1.0/1.1を無効にし、ハンドシェイクコストを下げるためにECDSA証明書を優先します。サーバーまたはリバースプロキシにスイート順を任せましょう。2024年頃のソフトウェアでは、ほとんどのデフォルトが妥当です。
実際の接続を検査する方法
ドキュメントを信頼する必要はありません — 自分のエンドポイントを検証しましょう。openssl s_clientは、ネゴシエートされたバージョン、暗号、証明書チェーンを表示します:
openssl s_client -connect example.com:443 -servername example.com -tls1_3 </dev/null
ProtocolとCipherの行を確認し、証明書チェーンの出力で有効期限とSANエントリをチェックします。チェーンが不完全な場合、ブラウザでは成功するが一部のクライアントでは失敗する可能性があり、これは「Chromeでは動くがcurlでは動かない」バグの典型的な原因です。
よくあるHTTPSの設定ミス
- 中間証明書の欠落 — サーバーがリーフ証明書のみを送信します。ブラウザは中間証明書を取得できる場合がありますが、APIクライアントは取得しないことがよくあります。
- 混在コンテンツ — HTTPSページがHTTP経由でスクリプトや画像を読み込みます。ブラウザはブロックまたはダウングレードします。ブラウザコンソールで監査しましょう。
- HSTSなし —
Strict-Transport-Securityがないと、HTTP経由の最初のリクエストがリダイレクト前に傍受される可能性があります。 - 弱いまたは古いプロトコルバージョン — レガシークライアントのためにTLS 1.0を有効にしておくと、すべての接続が静かに弱体化します。
- 証明書の期限切れ — 更新を自動化しましょう。手動のカレンダーリマインダーは戦略ではありません。
妥当なサーバー設定
Nginxの場合、最小限のモダンなブロックは次のようになります:
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;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers off;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 1d;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
}
fullchain.pemファイルが中間証明書欠落の問題を防ぎます。ポート80からのリダイレクトは別のサーバーブロックに置き、ACMEチャレンジのためにHTTPが引き続き機能するようにします。
HTTPSが保護しないもの
リクエストがサーバーに到達すると、TLSの役割は終わりです。インジェクションの欠陥、壊れたアクセス制御、ログ内の漏洩した秘密情報、設定ミスのCORSはすべてトランスポート層の上に存在します。TLSはベースラインであり、セキュリティプログラムではありません。インシデントをデバッグする場合は、トランスポートの問題(ハンドシェイクの失敗、期限切れの証明書)とアプリケーションの問題(500エラー、不正なペイロード)を早い段階で分離しましょう — 修正方法はまったく異なります。
FAQ
HTTPSはURLパスとクエリ文字列を暗号化しますか?
はい。HTTPSでは、パスとクエリ文字列を含むHTTPリクエスト全体が暗号化されたTLSレコード内にあります。ただし、Encrypted Client Helloが使用されていない限り、ハンドシェイク中にホスト名(SNI)と宛先IPは可視です。
TLS 1.3は常にTLS 1.2より高速ですか?
通常は高速です。往復を1回節約し、0-RTT再開をサポートするためです。高レイテンシ接続では差が顕著ですが、ローカルネットワークではわずかです。より大きな利点は、TLS 1.3がレガシー暗号オプションを完全に削除していることです。
HTTPからHTTPSにリダイレクトする場合でもHSTSは必要ですか?
はい。リダイレクトはブラウザがサーバーに到達した後にのみ役立ちます。HSTSはブラウザに将来のリクエストでHTTPを完全にスキップするよう指示し、最初のリクエストが傍受される可能性のあるウィンドウを閉じます。
デプロイメントを監査し、証明書の詳細を確認したり、キャプチャしたハンドシェイクを迅速にデコードする必要がある場合は、ターミナルベースのワークフローを手元に置いておきましょう。チーム向けに調査結果を文書化する場合は、Markdownエディタが、生のopenssl出力を読みやすいレポートに変換する迅速な方法です。