SREの日常に潜む暗号技術 ─ 「なんとなく」で済ませていませんか?
この投稿はEnglishでも表示されます。
はじめに
- CloudflareのTLS設定、選択肢が並んでいるけれど違いがわからないからデフォルトのまま
- デプロイ後にヘルスチェックが
certificate verify failedで落ちた。ブラウザでは普通に見れるのに
こういう場面に覚えがあるなら、この記事はきっと役に立ちます。
この記事ではまずHTTPS通信1回の裏側で何が起きているかを見た後、上の疑問を順に解消していきます。SREとして日常的に暗号に触れているのに、仕組みを聞かれると答えに詰まる。自分もそうでした。暗号技術を体系的に学び直してみると、日々の判断の精度がはっきり変わりました。
HTTPS通信の裏側で何が起きているか
ブラウザで https:// にアクセスしたとき、裏側では何が起きているのか。このサイト(shinagawa-web.com)はCloudflare Workersにデプロイしているので、自分のサイトを例に curl -v で覗いてみます。
$ curl -v https://shinagawa-web.com 2>&1 | grep -E "SSL|subject|issuer|expire|Cipher"
* SSL connection using TLSv1.3 / AEAD-CHACHA20-POLY1305-SHA256 / [blank] / UNDEF
* subject: CN=shinagawa-web.com
* expire date: Jun 15 04:47:03 2026 GMT
* issuer: C=US; O=Google Trust Services; CN=WE1
* SSL certificate verify ok.
たった数行の出力ですが、この中には鍵交換・共通鍵暗号・改ざん検知・デジタル署名・ハッシュ関数と、5種類の暗号技術が凝縮されています。
ただし、SREがこの5つすべてを深く理解する必要があるかというと、そうではありません。たとえば CHACHA20-POLY1305 と AES-256-GCM のどちらを使うかはCloudflareがクライアント環境に応じて自動選択するため、SREが選ぶ場面はありません。
SREが自分で判断を迫られるのは、主に次の2つです。
- TLSバージョンの選択 ─ 1行目の
TLSv1.3。Cloudflareの「Minimum TLS Version」をどれにするか - 証明書の信頼チェーン ─
issuerやsubjectに関わる部分。certificate verify failedのトラブルシュートに直結する
この記事では、この2つに絞って掘り下げていきます。
curl出力に含まれる5つの暗号技術
- 鍵交換(ECDH) ─ クライアントとサーバーが、盗聴されている通信路上で安全に共通の鍵を作り出す
- 共通鍵暗号(ChaCha20 / AES) ─ 作り出した共通鍵で、実際のHTTPデータを高速に暗号化する
- 認証付き暗号(Poly1305 / GCM) ─ 暗号化と同時に、データが途中で改ざんされていないことを保証する
- デジタル署名 ─ サーバー証明書が本物であることを、認証局(Google Trust Services)の署名で検証する
- ハッシュ関数(SHA-256) ─ 署名や改ざん検知の土台として、データの「指紋」を生成する
今回の出力では CHACHA20-POLY1305 が選ばれていますが、クライアント環境によっては TLS_AES_256_GCM_SHA384 が選ばれることもあります。ChaCha20はハードウェアにAES専用命令(AES-NI)がないモバイル端末で高速に動作する暗号方式で、AES-GCMはAES-NI搭載のサーバーやPCで高速です。どちらを選んでも、「共通鍵で暗号化+改ざん検知+ハッシュ」という構造は同じです。
TLSバージョン、どれを選ぶ?
Cloudflareのダッシュボードには「Minimum TLS Version」という設定があります。TLS 1.0 / 1.1 / 1.2 / 1.3 から選ぶだけのシンプルな画面ですが、大事なのは「TLS 1.3で行くか、1.2も残すか」です。
TLS 1.2を残さざるを得ないケース
古いHTTPクライアントライブラリやSDKを使った外部システムとの連携がある場合、TLS 1.2を残す必要があります。2026年現在、主要ブラウザはすべて1.3対応済みですが、組み込み機器や古いSDKを抱えた取引先システムなどは対応が追いついていないことがあります。
まず現状を把握するところから始めます。Cloudflareの場合、ダッシュボードの Analytics > Security でTLSバージョン別のトラフィック割合を確認できます。TLS 1.2の接続が全体の何%を占めているかがわかれば、「いつ切るか」の判断材料になります。1.2の割合がごく少量なら接続元を特定して個別に対応を依頼し、切り替えのスケジュールを引くことができます。
TLS 1.2を残す場合、そのまま有効にするだけでは注意が必要です。TLS 1.2のcipher suiteには、暗号化データのパディングを悪用される脆弱性が指摘されたCBCモードや、秘密鍵が漏洩すると過去の通信もすべて解読されてしまうRSA鍵交換が含まれています。Cloudflareはこのあたりを自動で最適化してくれますが、自前でnginxを運用する場合は ssl_ciphers でGCM系かつECDHEのみに絞り、弱いcipher suiteを明示的に排除する必要があります。
TLS 1.3なら構造的に安全
TLS 1.3ではcipher suiteが5つに絞り込まれ、CBCモードやRSA鍵交換など過去に問題になった方式は選択肢自体が存在しません。設定で弱い方式を選んでしまうリスクがなく、「正しく設定されているか」を心配する必要がありません。すべてのcipher suiteが前方秘匿性(秘密鍵が漏洩しても過去の通信は解読されない性質)を持ちます。
TLS 1.2を残すかどうかは、先述のトラフィック分析で判断します。1.2接続がゼロに近いならMinimum TLS Versionを1.3に上げる。残っているなら接続元を特定し、移行期限を決めて1.2を段階的に廃止する。「可能なら1.3にしたい」ではなく、データを見て切り替え時期を決めるのがSREの仕事です。
cipher suiteに登場する用語の簡単な紹介
ここまでのセクションで登場した暗号技術の用語を簡単にまとめます。
- AES(共通鍵暗号) ─ データを暗号化するエンジン。暗号化と復号に同じ鍵を使う。鍵長は128 / 192 / 256bitがあり、256bitが現在の推奨
- CBC / GCM(暗号利用モード) ─ AESで複数ブロックをどう処理するかの方式。CBCは脆弱性あり、GCMは暗号化+改ざん検知を同時に行うため現在の標準
- ChaCha20-Poly1305 ─ AES-GCMと同じ役割の認証付き暗号。AES専用ハードウェア(AES-NI)がないモバイル端末で高速
- ECDHE(鍵交換) ─ 盗聴されている通信路上で安全に共通鍵を作り出す仕組み。前方秘匿性(過去の通信が後から解読されない性質)を持つ
- RSA(公開鍵暗号) ─ 公開鍵と秘密鍵のペアを使う暗号。TLS 1.2では鍵交換にも使われたが、前方秘匿性がないためTLS 1.3では廃止
- SHA(ハッシュ関数) ─ データから固定長の「指紋」を生成する。改ざん検知やデジタル署名の土台
certificate verify failed ─ 何がどう失敗しているのか
TLSバージョンを正しく設定しても、証明書まわりのトラブルは別の形でやってきます。デプロイ後にヘルスチェックが落ちてアラートが来た。ブラウザで開くと普通に表示されるのに、ヘルスチェックのログには certificate verify failed とだけ出ている。このエラーを切り分けるには、証明書がどういう仕組みで信頼されるのかを知る必要があります。
そもそも証明書とは何か
最初のセクションの curl -v の出力を思い出してください。subject: CN=shinagawa-web.com と issuer: C=US; O=Google Trust Services; CN=WE1 がありました。これはGoogle Trust Servicesという認証局(CA)が「shinagawa-web.comは確かにこのサーバーである」と証明している、という意味です。TLS証明書とは、信頼された第三者(CA)がドメインの正当性を保証する電子的な証明書です。
証明書の信頼チェーン
証明書は単体で信頼されるわけではありません。ルートCA → 中間CA → サーバー証明書という連鎖があり、クライアントはこの連鎖をたどってルートCAに到達できるかを検証します。
よくある原因3パターン
中間CA証明書の未設定: サーバーに中間CA証明書が設定されていない。ブラウザはキャッシュで中間CAを補完するため表示できますが、ヘルスチェックやHTTPクライアントライブラリは補完しません。「ブラウザだけ動く」の正体はこれです。
証明書の期限切れ: Let's Encryptの自動更新が止まっている。certbotのcronが動いていないか、DNS-01チャレンジのAPI認証情報が期限切れになっているか。
ドメイン不一致: *.example.com のようにサブドメインをまとめて1枚の証明書でカバーするワイルドカード証明書がありますが、これは1階層のサブドメインのみ有効です。api.example.com にはマッチしますが、a.b.example.com にはマッチしません。
確認コマンド
$ openssl s_client -connect shinagawa-web.com:443 -showcerts < /dev/null 2>/dev/null
Certificate chain
0 s:CN=shinagawa-web.com
i:C=US, O=Google Trust Services, CN=WE1
1 s:C=US, O=Google Trust Services, CN=WE1
i:C=US, O=Google Trust Services LLC, CN=GTS Root R4
2 s:C=US, O=Google Trust Services LLC, CN=GTS Root R4
i:C=BE, O=GlobalSign nv-sa, OU=Root CA, CN=GlobalSign Root CA
---
Verify return code: 0 (ok)
s: が証明書の主体、i: がその証明書を発行した認証局です。0 → 1 → 2 と連鎖がつながり、最終的にGlobalSign Root CAに到達しています。Verify return code: 0 (ok) なら信頼チェーンに問題はありません。
期限とドメインの確認はこちらです。
$ echo | openssl s_client -connect shinagawa-web.com:443 2>/dev/null | openssl x509 -noout -dates -subject
notBefore=Mar 17 03:47:05 2026 GMT
notAfter=Jun 15 04:47:03 2026 GMT
subject=CN=shinagawa-web.com
原因がわかったら
中間CA未設定の場合: サーバーの証明書設定にフルチェーン(サーバー証明書+中間CA証明書)を設定します。nginxなら ssl_certificate にフルチェーンのファイルを指定します。Cloudflareを使っている場合、エッジ側の証明書はCloudflareが管理するためこの問題は起きません。
期限切れの場合: certbotなら certbot renew で更新します。DNS-01チャレンジを使っている場合はAPIトークンの有効期限も確認してください。Cloudflareの場合は自動更新されますが、オリジンサーバーの証明書は別管理なので注意が必要です。
ドメイン不一致の場合: 証明書のSAN(Subject Alternative Name)に対象ドメインが含まれているか確認します。ワイルドカード証明書なら、1階層のサブドメインのみ有効であることを踏まえて、必要なドメインをすべてカバーする証明書を取得し直します。
おわりに
冒頭で挙げた2つの疑問を振り返ります。
- CloudflareのTLS設定はどれを選ぶ? → トラフィック分析でTLS 1.2の接続割合を確認し、データに基づいて切り替え時期を判断する。1.2を残す場合はcipher suiteの制限に注意する
- certificate verify failedの原因は? → 中間CA未設定・期限切れ・ドメイン不一致の3パターン。
openssl s_clientで信頼チェーンを確認すれば切り分けられる
どちらも、仕組みを知っていれば原因の特定と対処の方針がすぐに立ちます。暗号技術は範囲が広いですが、SREの日常業務で判断を迫られるポイントは限られています。まずはその部分を押さえるだけで、トラブルシュートの速度と設定変更の自信が変わるはずです。