HTTPSとTLSの仕組みを徹底解説:ハンドシェイクと証明書

Security2026-10-27TryQuickToolBox

SSL証明書を追加し、ドメインをサーバーに向ければ、鍵アイコンが表示されます。ほとんどの開発者はそこで作業を終えます。しかしある日、APIクライアントがUNABLE_TO_VERIFY_LEAF_SIGNATUREで失敗したり、古いAndroidでモバイルアプリが証明書を拒否したり、ロードバランサーがTLSを終端してアプリがすべてを平文HTTPとして認識したりします。その時初めて、TCP接続と最初のHTTPリクエストの間で実際に何が起こっているのかを知る必要が出てきます。

この記事では、HTTPSとTLSが実際にどのように機能するかを解説します。ハンドシェイクが何をするのか、証明書がどのように検証されるのか、TLS 1.3で何が変わったのか、そして実際に遭遇するエラーの診断方法を説明します。

HTTPSが実際に追加するもの

HTTPSはTLS接続上で送信されるHTTPです。TLSは3つの保証を提供します:

このリストにないものに注意してください:クライアントはデフォルトでは認証されません。TLSはサーバーを認証し、アプリケーションがユーザーを認証します。

TLS 1.2ハンドシェイクの手順

TLS 1.2は古いインフラや多くの企業プロキシで依然として一般的なので、理解する価値があります。完全なハンドシェイクは次のようになります:

  1. ClientHello — クライアントはサポートするTLSバージョン、ランダム値、暗号スイートのリスト、(該当する場合は)SNIホスト名を送信します。
  2. ServerHello — サーバーはバージョンと暗号スイートを選択し、自身のランダム値と証明書チェーンを返します。
  3. 鍵交換 — ECDHEでは、サーバーは一時的な公開鍵を送信し、クライアントも自身の公開鍵を送信します。両者は同じ共有秘密を導出します。サーバーは証明書の秘密鍵でハンドシェイクトランスクリプトに署名します。
  4. Finished — 両側がハンドシェイクトランスクリプトのMACを送信し、何も改ざんされていないことを確認します。
  5. アプリケーションデータ — HTTPリクエストとレスポンスが暗号化されて流れます。

重要な詳細は一時的鍵交換です。ECDHEでは、接続ごとに新しい鍵ペアが生成されます。攻撃者が何年分ものトラフィックを記録し、後でサーバーの秘密鍵を盗んだとしても、過去のセッションを復号することはできません。この特性は前方秘匿性(forward secrecy)と呼ばれ、静的RSA鍵交換が非推奨となった主な理由です。

TLS 1.3が変えたもの

TLS 1.3はハンドシェイクを1往復に簡素化し、レガシーアルゴリズムを完全に削除しました。

項目TLS 1.2TLS 1.3
往復回数(完全ハンドシェイク)21
鍵交換RSAまたはECDHEECDHEのみ(前方秘匿性)
暗号スイート暗号、MAC、鍵交換が別々AEADスイートのみ、例:TLS_AES_128_GCM_SHA256
静的RSA、CBC、RC4許可削除
セッション再開セッションID / チケットPSKと0-RTTオプション

TLS 1.3では、クライアントが鍵交換グループを推測し、最初のメッセージで自身のシェアを送信するため、サーバーは即座に鍵合意を完了できます。証明書は依然として送信されますが、暗号化されるようになったため、受動的な観察者はどのホスト名が提供されているかを見ることができなくなりました。Encrypted Client Helloが使用されていない限り、ClientHelloのSNIのみがそれを漏らします。

0-RTT再開により、再訪するクライアントは最初のフライトでアプリケーションデータを送信できます。高速ですが、0-RTTデータはリプレイ可能であるため、支払いなどの非冪等リクエストには決して使用しないでください。

証明書とチェーン検証

証明書は公開鍵をホスト名にバインドします。検証はチェーンをたどる作業です:

unable to verifyエラーの最も一般的な原因は、中間証明書の欠落です。ブラウザは中間証明書をキャッシュしてこの問題を覆い隠すことがありますが、コマンドラインツールやモバイルアプリは厳密に失敗します。常に完全なチェーンを提供してください。リーフを先頭に、次に中間証明書を送信し、ルートは決して送信しないでください。

OpenSSLを使った実践的デバッグ

何かが壊れたときは、クライアントを責める前に、サーバーが実際に何を送信しているかを調べましょう。

# サーバーが提示する証明書チェーンを表示
openssl s_client -connect example.com:443 -servername example.com -showcerts

# TLS 1.3のみをネゴシエートし、選択された暗号を表示
openssl s_client -connect example.com:443 -tls1_3

# 証明書の有効期限を1行で確認
echo | openssl s_client -connect example.com:443 2>/dev/null \
  | openssl x509 -noout -dates -subject -ext subjectAltName

s_clientがverify error:num=21:unable to verify the first certificateを報告した場合、チェーンが不完全です。ホスト名の不一致を報告した場合は、SANリストを確認してください。ワイルドカード証明書は1つのラベルのみをカバーするため、*.example.comはapi.example.comに一致しますが、a.b.example.comには一致しません。

よくある設定ミス

妥当なサーバー設定

Nginxの場合、現代的なベースラインは次のようになります:

ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers off;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 1d;
ssl_session_tickets off;

add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header X-Content-Type-Options "nosniff" always;

TLS 1.3ではサーバーに暗号を選択させてください — スイートはすべて強力なので、クライアントの優先順位で問題ありません。チケットキーをローテーションしない限り、セッションチケットはオフにしてください。チケットキーが漏洩すると、再開が危険にさらされる可能性があります。

FAQ

HTTPSはホスト名を暗号化しますか?

デフォルトでは暗号化しません。ClientHelloのSNI拡張は、サーバーが提示すべき証明書を知るために平文で送信されます。Encrypted Client Hello(ECH)はそれを隠しますが、採用はまだ限定的です。パスとクエリ文字列は常に暗号化されます。

証明書がブラウザでは機能するのに、curlやモバイルアプリで失敗するのはなぜですか?

ほとんどの場合、中間証明書の欠落です。ブラウザは中間証明書を独自に取得またはキャッシュすることがよくありますが、より厳格なクライアントはサーバーが完全なチェーンを送信することを要求します。openssl s_client -showcertsで確認してください。

TLS 1.3は常にTLS 1.2より高速ですか?

新規接続の場合は、はい — 2往復ではなく1往復です。再開接続では差は縮まり、0-RTTは非冪等リクエストのリプレイリスクを犠牲にしてTLS 1.3をさらに高速にできます。

TLS変更後にWebサーバーを監査する場合、ログ分析は実際の影響を明らかにすることがよくあります:壊れたリダイレクト、予期しない4xxの急増、HTTPにまだアクセスしているクライアントなどです。Nginxログアナライザーにアクセスログを投入すると、ステータスコードの分布を確認し、HTTPSへのアップグレードを完了しなかったクライアントを特定できます。