「nginxでssl_ciphersを絞ったのに、TLS 1.3の暗号スイートが変わらないんです」
「新しいブラウザーにしたら、古いプロキシーの先のサイトにつながらなくなりました」
TLSは、ここ数年で大きく変わりました。TLS 1.3の登場、古い方式の禁止、SNIの暗号化(ECH)、そして耐量子計算機暗号(PQC)の導入。仕組みが変わったことを知らないと、こうした「設定したのに変わらない」「急につながらない」の原因にたどり着けません。
第6回までで、証明書の中身と発行の流れを見てきました。第7回では、その証明書が実際の通信でどう使われるか、TLSの仕組みを見ていきます。
SSLからTLS 1.3まで
| バージョン | 公開 | 仕様 | 現在の扱い |
|---|---|---|---|
| SSL 2.0 | 1995年 | Netscape の仕様 | 使用禁止(RFC 6176、2011年) |
| SSL 3.0 | 1996年 | RFC 6101(歴史的記録) | 使用禁止(RFC 7568、2015年)。POODLE |
| TLS 1.0 | 1999年 | RFC 2246 | 使用禁止(RFC 8996、2021年) |
| TLS 1.1 | 2006年 | RFC 4346 | 使用禁止(RFC 8996、2021年) |
| TLS 1.2 | 2008年 | RFC 5246 | 使用可。ECDHE と AEAD に限る |
| TLS 1.3 | 2018年 | RFC 8446 で標準化、2026年7月に RFC 9846 へ改訂 | 推奨。新しいプロトコルは TLS 1.3 必須(RFC 9852) |
RFC 8996は「MUST NOT(使ってはならない)」で、「非推奨」ではありません。RFC 9846はTLS 1.2の仕様(RFC 5246)なども廃止扱いにし、TLS 1.2の実装への要件を引き継ぎました。TLS 1.2の使用が禁止されたわけではありません。この記事ではTLS 1.3をRFC 9846として扱います。
古い方式が使えなくなった理由
SSL・TLSの歴史は、見つかった弱点を塞いできた歴史です。

- SSL 3.0:パディングの検査の甘さを突いて平文を割り出すPOODLE攻撃(2014年)で致命傷を負った
- TLS 1.0:CBCモードの弱点を突くBEAST攻撃(2011年)があった
- TLS 1.0と1.1:認証付き暗号(AEAD)を使えず、SHA-1にも頼っていたため、2021年のRFC 8996で使用禁止に。暗号ではRC4も2015年に禁止された
- 2026年7月のRFC 10015:TLS 1.2でも、RSA鍵交換と有限体のDH(DHE)鍵交換が使用禁止になった。RSA鍵交換には前方秘匿性(第2回)がなく、サーバーの秘密鍵が後で漏れると、記録されていた過去の通信がすべて解読されるから
結果として、TLS 1.2で使ってよいのはECDHEとAEADの組み合わせにほぼ限られ、TLS 1.3と同じ考え方になりました。今使うのは、TLS 1.3と、TLS 1.2(ECDHE+AEAD)です。
TLS 1.3のハンドシェイク
TLS 1.3では、ハンドシェイクが1往復(1-RTT)で終わります。

- クライアントはClientHelloで、対応する暗号スイートや署名方式、接続先の名前(SNI)に加え、鍵交換の材料(key_share)を最初から送る。選ばれそうな方式を予想して入れておく
- サーバーはServerHelloで方式を選び、自分のkey_shareを返す。この時点で両者は同じ秘密を計算でき、以降は暗号化される
- 続けて証明書(Certificate)、秘密鍵による署名(CertificateVerify)、Finishedなどを暗号化して送る
- クライアントは証明書を検証し(第5回)、Finishedを返してデータを送り始める
証明書も暗号化されて届くため、途中の機器からは中身が見えません。予想が外れると、サーバーがHelloRetryRequestで方式を指定し、1往復増えます。最初の1通に鍵の材料を入れるから、2通目から暗号化できるわけです。
メッセージの役割
| メッセージ | 向き | 暗号化 | 役割 |
|---|---|---|---|
| ClientHello | C→S | なし | 対応するバージョン・暗号スイート・グループ・署名方式、key_share、SNI、ALPN |
| ServerHello | S→C | なし | 選んだ暗号スイートとグループ、サーバーの key_share |
| EncryptedExtensions | S→C | あり | ALPN の結果など、暗号化してよい拡張 |
| CertificateRequest | S→C | あり | クライアント証明書を求める(mTLS のときだけ) |
| Certificate | S→C | あり | サーバー証明書と中間証明書 |
| CertificateVerify | S→C | あり | ここまでのやり取りへの署名。証明書の秘密鍵を持つことの証明 |
| Finished | 双方向 | あり | やり取り全体の MAC。途中で改ざんされていないことの確認 |
| NewSessionTicket | S→C | あり | 次回の再開用のチケット |
TLS 1.3では、証明書の秘密鍵は鍵交換に使われず、CertificateVerifyで「この証明書の持ち主が、今このやり取りをしている」ことを示す署名にだけ使われます。鍵交換は毎回使い捨てのECDHEで行うため、証明書の秘密鍵が後で漏れても過去の通信は解読されません(前方秘匿性)。
TLS 1.2(ECDHE)との違い

TLS 1.2のECDHEによるハンドシェイクは2往復です。TLS 1.3との違いは3つあります。
- 鍵交換の材料を2往復目に送るため、遅い
- 証明書が平文で流れる。同じ証明書でも、1.2では途中の機器から中身が見える
- 古い方式を選べてしまう
よく見かける「プリマスターシークレットをサーバーの公開鍵で暗号化して送る」という図は、RSA鍵交換の図です。前方秘匿性がないためTLS 1.3で廃止され、TLS 1.2でもRFC 10015で禁止されました。TLS 1.2を残すなら、ECDHEとAEADの暗号スイートだけを許可します。
暗号スイートの読み方(1.2と1.3)
| 項目 | TLS 1.2 | TLS 1.3 |
|---|---|---|
| 例(IANA の名前) | TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 | TLS_AES_128_GCM_SHA256 |
| 例(OpenSSL の名前) | ECDHE-RSA-AES128-GCM-SHA256 | TLS_AES_128_GCM_SHA256(同じ) |
| 名前が表すもの | 鍵交換(ECDHE)・認証(RSA)・暗号(AES-128-GCM)・ハッシュ | 暗号(AEAD)とハッシュだけ |
| 鍵交換の決め方 | 暗号スイートで決まる | supported_groups・key_share 拡張(X25519MLKEM768 等) |
| 署名の決め方 | 暗号スイート+signature_algorithms | signature_algorithms 拡張 |
| 種類 | 数百(古いものを含む) | 5つ(主に使うのは3つ) |
| OpenSSL・nginx での設定 | -cipher、ssl_ciphers | -ciphersuites、ssl_conf_command Ciphersuites |
TLS 1.3で主に使うのは、AES-128-GCM・AES-256-GCM・ChaCha20-Poly1305の3つです。
冒頭の「ssl_ciphersを絞ったのに変わらない」の答えがここにあります。nginxのssl_ciphersはTLS 1.2以前の設定で、TLS 1.3の暗号スイートは変わりません。TLS 1.3はssl_conf_command Ciphersuitesで設定します。WindowsではGet-TlsCipherSuiteで、有効な暗号スイートと順序を確認できます。
SNI・ALPN・ECH
- SNI(Server Name Indication):ClientHelloで接続したい名前を伝える拡張。複数のサイトを動かすサーバーは、SNIを見て返す証明書を選ぶ
- ALPN:HTTP/2(h2)とHTTP/1.1のどちらを使うかを決める拡張

ただしSNIは平文なので、途中の機器から接続先が見えます。これを隠すのがECH(Encrypted Client Hello、RFC 9849、2026年3月)です。
- クライアントは、DNSのHTTPSレコード(連載「DNS入門」第6回)からECHの公開鍵を得て、本当のClientHelloを暗号化し、外側には共通の名前だけを見せる
- Chromeは117、Firefoxは119から既定で有効
注意したいのは、SNIで振り分けや監視をする社内のプロキシーやファイアウォールは、ECHの通信を見分けられなくなることです。
セッション再開と0-RTT
フルのハンドシェイクは時間がかかるため、一度接続した相手とは、前回の結果を使って短い手順で再接続します。

- TLS 1.2ではセッションIDやセッションチケット、TLS 1.3ではNewSessionTicketで渡されるチケット(PSK、事前共有鍵)を使う。TLS 1.3ではPSKと新しいECDHEを組み合わせて、前方秘匿性も保てる
- 注意点は、チケットを暗号化するサーバー側の鍵(チケットキー)。長く変えずにいて漏れると多くの通信が解読されるため、定期的に入れ替える
さらにTLS 1.3には、再開時にClientHelloと一緒に最初のデータを送る0-RTT(早期データ)があります。速くなる代わりに、記録した早期データを送り直すリプレイ攻撃を防げず、同じ「注文」が2回処理されるおそれがあります。0-RTTは既定で無効のサーバーが多く、使うなら副作用のない処理に限ります。迷ったら無効のままにしましょう。
クライアント証明書(mTLS)
サーバーがCertificateRequestを送り、クライアントも証明書と署名(CertificateVerify)を返して互いに認証する方式を、相互TLS(mTLS、クライアント証明書認証)と呼びます。

- 通常のTLSと違い、秘密鍵を持つ端末だけが接続できるため、パスワードを盗まれても使えない
- 802.1X(EAP-TLS)、VPN、サービス間のAPI通信などで使い、証明書は社内CA(第9回)で発行するのが基本
- 以前は公開CAの証明書にもクライアント認証の用途(EKUのclientAuth)が入っていたが、Chromeのルートプログラムの要件の変更で、Let’s Encryptは2026年2月にこれを外した。流用している構成は見直す
- TLS 1.3はRSAのPKCS#1 v1.5署名を認めないが、古いICカードなどのため、2026年4月のRFC 9963でクライアント証明書に限り例外が設けられた
サーバー証明書と同じ確認を、逆向きにもう1回行う、と考えると分かりやすいでしょう。
PQCハイブリッド鍵交換(X25519MLKEM768)
冒頭の2つ目、「新しいブラウザーで古いプロキシーの先につながらない」の原因になりうるのが、これです。

量子コンピューターが実用化されるとECDHEやRSAは解かれるおそれがあり、今の通信を記録して将来解読する「Harvest Now, Decrypt Later」攻撃に備えて、鍵交換から移行が進んでいます(第2回)。
- 使われるのは、従来のX25519と、FIPS 203のML-KEM-768を組み合わせるX25519MLKEM768(RFC 10024、2026年8月)。両方を混ぜて鍵を作るため、片方が破られても安全
- Chrome 131、Firefox 132、iOS・macOS 26、OpenSSL 3.5が既定で提示する。WindowsのSchannelも2026年に対応したが既定では無効
- 注意点は、key_shareが約1.2KB増えて、ClientHelloが1パケットに収まらなくなること。これを扱えない古い機器やミドルボックスでは、接続に失敗する
変わったのは鍵交換だけで、証明書の署名は今もECDSAやRSAです。
推奨設定(RFC 9325とMozillaの設定例)
| 項目 | Modern | Intermediate(一般的な推奨) |
|---|---|---|
| TLS バージョン | 1.3 のみ | 1.2、1.3 |
| TLS 1.3 の暗号スイート | AES-128-GCM、AES-256-GCM、ChaCha20-Poly1305 | 同左 |
| TLS 1.2 の暗号スイート | — | ECDHE+AES-GCM/ChaCha20-Poly1305 の6つだけ |
| 鍵交換のグループ | X25519MLKEM768、X25519、P-256、P-384 | 同左 |
| 証明書 | ECDSA(P-256・P-384) | ECDSA、または RSA 2048ビット以上 |
| HSTS | max-age=63072000(2年) | 同左 |
| 想定する最も古いクライアント | Chrome 70、Firefox 63、Android 10、Safari 12.1 | IE 11(Windows 10)、Android 4.4.2、Java 8u161 |
Mozilla SSL Configuration Generator(ssl-config.mozilla.org、ガイドライン 6.0)は、nginx・Apache等の設定例をサーバーのバージョンに合わせて出してくれます。迷ったらIntermediateを選びます。RFC 9325(2022年)はTLS 1.2以上を必須・TLS 1.3を推奨とし、RFC 9852(2026年)は新しいプロトコルにTLS 1.3を必須としました。
接続を調べる:openssl s_client
$ openssl s_client -connect www.example.jp:443 -servername www.example.jp </dev/null
(抜粋)
Certificate chain
0 s:CN=www.example.jp
i:C=JP, O=Example Trust, CN=Example Issuing CA
a:PKEY: EC, (prime256v1); sigalg: ecdsa-with-SHA256
v:NotBefore: Jul 29 22:10:08 2026 GMT; NotAfter: Oct 27 22:17:21 2026 GMT
1 s:C=JP, O=Example Trust, CN=Example Issuing CA
i:C=JP, O=Example Trust, CN=Example Root CA
Negotiated TLS1.3 group: X25519MLKEM768
Verification: OK
New, TLSv1.3, Cipher is TLS_AES_256_GCM_SHA384
Verify return code: 0 (ok)
-servernameを必ず付ける(SNI)。付けないと既定の証明書が返り、名前の不一致と誤解する- Certificate chain:0がサーバー証明書、1以降が中間証明書。各行のi:(発行者)が次の番号のs:(主体)とつながっているかを見る(第4回)。v:で有効期間も分かる
Verification: OKは、手元の信頼ストアでチェーンの検証に成功したという意味。名前の一致は既定では確かめないので、-verify_hostname www.example.jpを付ける。失敗すると10 (certificate has expired)のように番号と理由が出る(第10回)
s_clientの主なオプション
| やりたいこと | オプション・コマンド | 見るところ |
|---|---|---|
| 名前の一致も検証する | -verify_hostname www.example.jp | Verified peername:、不一致は 62 (hostname mismatch) |
| バージョンを指定して試す | -tls1_2/-tls1_3 | 接続できるか、Protocol: |
| 鍵交換のグループを指定する | -groups X25519MLKEM768 | Negotiated TLS1.3 group: |
| ALPN を送る | -alpn h2,http/1.1 | ALPN protocol: h2 |
| 送られたチェーンを PEM で全部見る | -showcerts | 中間証明書が送られているか |
| 要約だけ見る | -brief | Protocol version:、Ciphersuite: |
| メールの STARTTLS | -starttls smtp(imap・pop3 等) | メールサーバーの証明書 |
| 期限と SAN だけ見る | s_client の出力をパイプで openssl x509 -noout -dates -ext subjectAltName に渡す | notAfter と SAN |
-tls1_2で接続すると、ECDHEの鍵交換はPeer Temp Key: X25519, 253 bitsのように表示されます。OpenSSL 3は既定のセキュリティレベルでTLS 1.0/1.1を送らないため、-tls1_1は手元で「no protocols available」になり、サーバーの設定は分かりません。サーバーが受け付ける版の一覧は、次のnmapなどで調べます。
curlとWindows(PowerShell)で調べる
$ curl -v -o /dev/null https://www.example.jp/ # (抜粋)
* ALPN: curl offers h2,http/1.1
* SSL connection using TLSv1.3 / TLS_AES_256_GCM_SHA384 / X25519MLKEM768 / id-ecPublicKey
* ALPN: server accepted h2
* Server certificate:
* subject: CN=www.example.jp
* expire date: Oct 27 22:17:21 2026 GMT
* subjectAltName: "www.example.jp" matches cert's "www.example.jp"
* SSL certificate verified via OpenSSL.
PS> $tcp = [System.Net.Sockets.TcpClient]::new('www.example.jp', 443)
PS> $ssl = [System.Net.Security.SslStream]::new($tcp.GetStream())
PS> $ssl.AuthenticateAsClient('www.example.jp') # 検証(名前を含む)に失敗すると例外
PS> $ssl.SslProtocol # Tls13
PS> $ssl.NegotiatedCipherSuite # TLS_AES_256_GCM_SHA384(PowerShell 7)
PS> ([System.Security.Cryptography.X509Certificates.X509Certificate2]$ssl.RemoteCertificate).NotAfter
PS> $ssl.Dispose(); $tcp.Dispose()
Test-NetConnection www.example.jp -Port 443は、TCPの到達性だけを見る。TLSと証明書は上のSslStreamで確かめる- PowerShell 7なら
Invoke-WebRequest https://www.example.jp -SslProtocol Tls12で、指定した版で接続できるかを試せる - サーバーが受け付ける版と暗号スイートの一覧は
nmap --script ssl-enum-ciphers -p 443 www.example.jpで取れる(許可を得た対象にだけ使う)
考えてみよう
- 自分の担当サーバーは、TLS 1.0/1.1やRSA鍵交換(TLS_RSA_WITH_…)の暗号スイートをまだ受け付けていませんか。どうやって確かめますか。
- 社内のプロキシーやファイアウォールは、SNIを見て通信を制御していますか。ECHや大きなClientHello(PQC)が広がると、何が起きそうですか。
- クライアント証明書(mTLS)を使っている仕組みはありますか。その証明書はどのCAが発行し、失効はどう確かめていますか。
ヒント:この記事の「古い方式が使えなくなった理由」「SNI・ALPN・ECH」「mTLS」「PQCハイブリッド鍵交換」と、確認コマンドが手がかりです。
まとめ
- 今使うのはTLS 1.3とTLS 1.2(ECDHE+AEAD)。RSA鍵交換は2026年に1.2でも禁止
- TLS 1.3は1往復で暗号化が始まり、証明書も暗号化される。証明書の秘密鍵は署名にだけ使う
- TLS 1.3の暗号スイートはAEADとハッシュだけ。nginxの
ssl_ciphersでは変わらない - SNIは平文、ECHで暗号化。SNIを見る社内の機器に影響する
- 0-RTTはリプレイを防げない。mTLSの証明書は社内CAで発行する
- PQCのX25519MLKEM768は既定で使われ始めた。大きなClientHelloで古い機器が失敗しうる
次回は、証明書の期限切れを防ぐための棚卸し、ACMEによる自動更新、期限の監視を扱います。
連載「証明書入門」全11回
- なぜ証明書が必要か
- 暗号の基礎
- openssl・PowerShellで試す
- X.509証明書の中身
- PKIと信頼の仕組み
- 証明書の種類と発行
- TLSの仕組み(この記事)
- ライフサイクルと自動化
- 社内PKIとクラウド
- 証明書トラブルシュート
- 証明書の設計演習

コメント