TLS 1.3への強制移行:なぜ「古い暗号」を今すぐ抹殺すべきなのか
現場でインシデント対応をしていると、常に痛感させられることがある。それは、攻撃者は「最新の脆弱性」よりも「放置された過去の遺物」を好むという現実だ。
特にTLS 1.2以前、あるいはRSA鍵交換を用いた暗号スイートを有効にしたままのサーバーは、いわば「玄関の鍵をかけ忘れた家」に等しい。なぜ今、我々がTLS 1.3への強制移行と、Perfect Forward Secrecy(PFS)の担保に執着するのか。その「泥臭い理由」と「現場での実装解」を共有しよう。
—
なぜRSA鍵交換は「死んだ」のか?
TLS 1.2以前のRSA鍵交換には、致命的な盲点がある。サーバーの秘密鍵が万が一漏洩した場合、過去に傍受されたすべての通信データを遡って復号できてしまうからだ。これを防ぐのがPFS(前方秘匿性)を保証するECDHE(楕円曲線ディフィー・ヘルマン鍵交換)だ。
さらに、TLS 1.3では、これまでの複雑で脆弱な暗号スイートの組み合わせ(ネゴシエーション)を大幅に簡素化した。攻撃者が「あえて古い弱い暗号を選ばせる」というダウングレード攻撃も、TLS 1.3のプロトコル設計自体によって事実上封じられている。
これを怠ると、MITM(中間者)攻撃により、トラフィックを丸裸にされるリスクを許容し続けることになる。
—
実践:CloudFrontで「堅牢なTLS 1.3」を強制する
AWSを利用しているなら、インフラ層での防御が最もコスト対効果が高い。CloudFrontのカスタムSSL証明書設定において、レガシーな設定を排除する手順を解説する。
CloudFront セキュリティポリシーの設定
CloudFrontの「Security Policy」は、必ず TLSv1.2_2021 以降を選択すること。これにより、TLS 1.1以下の古いプロトコルを即座に無効化し、かつPFSをサポートしない暗号スイートを排除できる。
設定のポイント:
- Minimum TLS Version:
TLSv1.2(AWSのUI上、TLS 1.3を有効にするには1.2以上を選択する必要がある) - Cipher Suites:
ECDHE-RSA-AES128-GCM-SHA256などのPFS対応スイートのみに限定される。
—
Nginxで暗号スイートを鉄壁にする設定
クラウドの前段にNginxを置いている場合や、オンプレミス環境では、設定ファイル(nginx.conf)での明示的な指定が必須だ。
# /etc/nginx/conf.d/ssl.conf
# 古いTLS 1.0/1.1は論外として、TLS 1.3を最優先に定義
ssl_protocols TLSv1.2 TLSv1.3;
# PFS(前方秘匿性)を保証する暗号スイートのみを許可
# RSA鍵交換を排除し、ECDHE(楕円曲線)のみに絞る
ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384';
# サーバー側の暗号スイート選択を強制(クライアント側の弱い要求を無視する)
ssl_prefer_server_ciphers on;
# セッションチケットの無効化(前方秘匿性を高めるための微調整)
ssl_session_tickets off;
—
開発者が知るべき「アプリ層でのTLS」と注意点
「TLSはインフラ担当の仕事」と考えているなら、それは甘い。アプリケーションがバックエンドAPIと通信する際、古いライブラリを使っていれば、そこが穴になる。
例えば、Pythonの requests やPHPの curl を使う際、OS側の OpenSSL が古いと、強固な設定を施したサーバーへの接続を拒否されることがある。
Pythonでのセキュアなリクエスト例
import requests
from requests.adapters import HTTPAdapter
from urllib3.util.ssl_ import create_urllib3_context
# TLS 1.3を強制し、レガシーな暗号を排除するアダプターを作成
class TLS13Adapter(HTTPAdapter):
def init_poolmanager(self, *args, **kwargs):
context = create_urllib3_context(ssl_version=19) # 19はTLS 1.3を指す
context.set_ciphers('ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256')
kwargs['ssl_context'] = context
super().init_poolmanager(*args, **kwargs)
# API通信時にこのセッションを使用する
session = requests.Session()
session.mount('https://api.your-secure-service.com', TLS13Adapter())
—
まとめ:セキュリティは「足し算」ではなく「引き算」
最後に一つだけ伝えておきたい。セキュリティにおいて重要なのは、新しい技術を足すことではない。「必要のない古い機能を削ぎ落とすこと」だ。
TLS 1.3への移行は、単なるバージョンアップではない。これまで通信経路に存在していた「暗号の抜け穴」を物理的に塞ぐ作業だ。
1. 古いTLS(1.0, 1.1)を即座にDisableにする。
2. RSA鍵交換を含む暗号スイートを排除し、ECDHEへ移行する。
3. インフラのCLI/コンソールだけでなく、アプリ側のライブラリも追従させる。
これらを実施し、ssllabs.com 等のツールで「A+」評価が得られる状態を維持してほしい。それが、エンジニアとして信頼を勝ち取るための最低限の作法だ。健闘を祈る。
コメント