SSL/TLS のベストプラクティス
安全な SSL/TLS の実装は、通信経路上のデータを傍受・改ざん、そして機密通信の機密性と完全性を損なうおそれのある中間者攻撃から保護するうえで不可欠です。SSL/TLS は広く採用されているものの(現代のほぼすべてのサイトが HTTPS を使用しています)、多くの実装には不適切な構成に起因する脆弱性が依然として残っています。POODLE、BEAST、CRIME といった既知の脆弱性のために IETF や PCI-DSS などの組織によって公式に非推奨(deprecated)とされた旧式のプロトコルバージョン(SSL 2.0、SSL 3.0、TLS 1.0、TLS 1.1)の使用、現代の計算資源で攻撃可能な RC4、DES、3DES、MD5 といった脆弱もしくは破られた暗号スイート、現在では安全でないとされる SHA-1 署名アルゴリズムを用いた証明書、将来サーバーの秘密鍵が侵害された場合に捕捉済みのトラフィックを遡って復号できてしまう完全前方秘匿性(PFS)の欠如、攻撃者が暗号化されていない HTTP 接続を強制するダウングレード攻撃を可能にする HSTS(HTTP Strict Transport Security)の不在、そしてデバイスにインストールされた悪意ある証明書による傍受を許してしまう重要なモバイルアプリケーションにおける不適切または不在の証明書ピンニング(certificate pinning)などです。最適化された SSL/TLS 構成は、セキュリティ態勢を向上させるだけでなく、パフォーマンス(TLS 1.3 のハンドシェイクは TLS 1.2 より高速です)、互換性(非常に古いクライアントは現代的な構成をサポートしない場合があります)、コンプライアンス(LGPD、GDPR、PCI-DSS は通信経路上のデータに対して強力な暗号化を要求します)にも影響します。本記事では、業界のベストプラクティス、ならびに NIST、Mozilla SSL Configuration Generator、OWASP の推奨事項に従って SSL/TLS を実装するための詳細な技術ガイドラインを提供します。
プロトコルバージョン:TLS 1.2 と TLS 1.3
すべてのサーバーで SSL 2.0、SSL 3.0、TLS 1.0、TLS 1.1 を完全に無効化してください。これらのバージョンには文書化された脆弱性があり、レガシークライアントとの互換性のためであっても使用すべきではありません。Web サーバー(nginx、Apache、IIS)またはロードバランサー(AWS ALB、Azure Application Gateway)を、TLS 1.2 と TLS 1.3 のみを受け入れるように構成してください。2018 年に RFC 8446 として承認された TLS 1.3 は、以前のバージョンに対して大きな改善をもたらします。より高速なハンドシェイク(2-RTT ではなく 1-RTT で、再開セッション(resumed sessions)向けに 0-RTT をサポート)、安全でない暗号スイートの削除(RSA 鍵交換、CBC モード暗号、RC4、3DES をもはやサポートしません)、すべての暗号スイートでの前方秘匿性の必須化、そしてプロトコルネゴシエーションのメタデータを隠蔽する暗号化されたハンドシェイクです。nginx では "ssl_protocols TLSv1.2 TLSv1.3;" で構成します。Apache では "SSLProtocol -all +TLSv1.2 +TLSv1.3" です。AWS ALB ではプロトコルと暗号を定義するセキュリティポリシー(security policy)を選択できます。現代的な環境では "ELBSecurityPolicy-TLS13-1-2-2021-06" を、いくつかの古いクライアントをサポートする必要がある場合は "ELBSecurityPolicy-2016-08" を選択してください。アクセスログを監視して、どれだけのクライアントが依然として TLS 1.0/1.1 を使用しているかを把握してください。その数が無視できるほど少なければ(トラフィックの 0.1% 未満)、実質的な影響なく無効化できます。両方のエンドポイントを管理する B2B API では、TLS 1.3 を専ら要求してください。プロトコルバージョンはハンドシェイク中にネゴシエートされることを覚えておいてください。サーバーはサポートするバージョンを通知し、クライアントは自身もサポートする最も高いバージョンを選択します。
暗号スイート:ECDHE と AES-GCM
暗号スイートは、TLS 接続中の鍵交換、認証、対称暗号化、HMAC に使用されるアルゴリズムを定義します。選択が不適切だと、TLS 1.2+ を使用していても通信全体のセキュリティが損なわれるおそれがあります。鍵交換には完全前方秘匿性を提供する ECDHE(Elliptic Curve Diffie-Hellman Ephemeral)を使用する暗号スイートを優先し、対称暗号化には、機密性と完全性の両方を保証する AEAD(Authenticated Encryption with Associated Data)暗号である AES-GCM(Galois/Counter Mode)または ChaCha20-Poly1305 を優先してください。安全な暗号スイートの例:TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 または TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256。以下は完全に避けてください。RC4 を用いる暗号スイート(統計的バイアスに対して脆弱)、エクスポート暗号(export ciphers、古い輸出規制により 40 ビットまたは 56 ビットに制限)、NULL 暗号(暗号化なし)、DES と 3DES(ブロックサイズが小さく Sweet32 に対して脆弱)、TLS 1.0-1.1 における CBC モード(BEAST と Lucky13 に対して脆弱)、そして DHE/ECDHE を伴わない RSA 鍵交換(前方秘匿性なし)。nginx では "ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384';" を構成し、サーバー側の順序を強制するために "ssl_prefer_server_ciphers on;" を設定します。ssllabs.com/ssltest などのツールを使用して構成を検証し、A+ 評価を取得してください。TLS 1.3 では、暗号スイートは簡素化され、デフォルトでより安全になっています(TLS_AES_128_GCM_SHA256、TLS_AES_256_GCM_SHA384、TLS_CHACHA20_POLY1305_SHA256)。
HSTS:HTTP Strict Transport Security
HSTS は、特定のドメインに対して常に HTTPS を使用するようブラウザに指示する仕組みで、SSL ストリッピング攻撃を防ぎます。この攻撃では、中間者が HTTPS への 301 リダイレクトが発生する前に最初のリクエストを傍受することで、暗号化されていない HTTP 接続を強制します。HSTS は、サーバーのすべての HTTPS レスポンスに "Strict-Transport-Security" ヘッダーを追加することで実装します:"Strict-Transport-Security: max-age=31536000; includeSubDomains; preload"。max-age パラメーター(秒単位)は、ブラウザが HTTPS のみを使用することをどれだけの期間記憶すべきかを定義します。31536000 秒 = 1 年が推奨値です。"includeSubDomains" はこのポリシーをすべてのサブドメインに適用します(すべてのサブドメインが HTTPS をサポートしていることを保証する必要があるため、慎重に使用してください)。"preload" は、ブラウザによって維持される HSTS preload list にドメインを含めたいことを示します。これは、いかなる HSTS ヘッダーを受信する前の初回アクセス時であっても常に HTTPS を使用しなければならないドメインの、ハードコードされたリストです。preload list に含めてもらうには、次の各点を確認したうえで hstspreload.org にドメインを申請してください。サイト全体が HTTPS で完全に動作すること、ドメインとサブドメインをカバーする有効な証明書を持っていること、すべての HTTP トラフィックを HTTPS にリダイレクトすること、ベースドメインで includeSubDomains および preload ディレクティブ付きの HSTS ヘッダーを提供すること、そして max-age が 31536000 以上であること。重要:preload は一方向の決定です。リストからドメインを削除するには数か月かかることがあり、その期間中に HTTPS をもはやサポートできない場合はユーザーのアクセスが壊れます。nginx では:"add_header Strict-Transport-Security 'max-age=31536000; includeSubDomains; preload' always;"。Apache では:"Header always set Strict-Transport-Security 'max-age=31536000; includeSubDomains; preload'"。
重要なアプリケーションのための証明書ピンニング(Certificate Pinning)
証明書ピンニング(certificate pinning)は、アプリケーション(特にモバイルアプリや重要な API)がサーバーの想定される証明書のハッシュ/フィンガープリントをハードコードまたは埋め込み、TLS ハンドシェイク中に提示された証明書が保存された pin と一致することを検証する手法です。これにより、攻撃者が(マルウェアや物理アクセスを介して)被害者のデバイスに悪意ある証明書をインストールし、通常はオペレーティングシステムの既定の CA チェーンによって信頼される HTTPS トラフィックを傍受する攻撃を防ぎます。ピンニングには 2 種類あります。リーフ証明書(leaf certificate)の pin(その証明書に固有で、証明書の有効期限切れ/更新時に更新が必要)、または CA の公開鍵/公開鍵の pin(より柔軟で、同じ CA によって署名されている限り証明書のローテーションが可能)です。モバイルアプリではネイティブフレームワークを使用してピンニングを実装します。iOS の Network フレームワークは URLSessionDelegate のコールバックを介したピンニングを可能にし、Android は pin-set 要素を持つ Network Security Configuration XML を使用し、React Native は react-native-ssl-pinning ライブラリを使用できます。API については、クライアントもサーバーに証明書を提示し、それをピンニングで検証する相互 TLS(mTLS)を検討してください。注意:ピンニングが誤っていると、pin した(pinned)証明書へのアクセスを失いフォールバック機構がない場合に、アプリケーションが動作しなくなるおそれがあります。常に次を実装してください。バックアップ pin(2 つ目の有効な証明書/CA)、無線(over-the-air)による pin 更新、そして緊急時のロールバックを可能にするために pin を強制する前の猶予期間です。可能な場合は、証明書ピンニング(Certificate Pinning)ではなく公開鍵ピンニング(Public Key Pinning)を使用してください(更新に対してより堅牢です)。SHA-256 ダイジェストと公開鍵の base64 ハッシュを用いた Android での構成例。openssl などのツールを使用すると、pin に使用するために証明書から公開鍵ハッシュを抽出できます。
完全前方秘匿性(Perfect Forward Secrecy、PFS)
完全前方秘匿性(Perfect Forward Secrecy)は、サーバーの長期的な秘密鍵(SSL 証明書の秘密鍵)が侵害されても、捕捉済みの過去のセッションを復号できないという暗号学的性質です。これは、各セッションに対して動的に生成され、使用後に破棄される一時鍵(ephemeral keys)を用いることで実現されます。PFS がない場合、攻撃者がすべての暗号化トラフィックを捕捉し(ネットワークタップ(network taps)で容易に可能)、数年後にあなたの秘密鍵を窃取できれば(侵害、内部脅威、裁判所命令を通じて)、保存された pcap に立ち返り、過去のすべてのセッションを復号できてしまいます。これは「遡及的復号(retrospective decryption)」として知られるシナリオです。PFS は、派生に証明書の秘密鍵を必要としない Diffie-Hellman アルゴリズムを通じて一意のセッション鍵(session keys)を生成することで、これを防ぎます。たとえ秘密鍵が侵害されても、以前のセッション鍵はもはや存在しない一時的なコンポーネントで生成されているため、安全なままです。PFS を確保するには、鍵交換に DHE(Diffie-Hellman Ephemeral)または ECDHE(Elliptic Curve DHE)を用いる暗号スイートのみを使用してください。PFS を提供しない RSA 鍵交換は避けてください。セッション鍵がサーバーの公開鍵で暗号化され、秘密鍵で復号されるためです。TLS 1.3 では PFS は必須です(すべての暗号スイートが ECDHE を使用します)。TLS 1.2 では、サーバーが RSA よりも ECDHE 暗号を優先するように構成してください。次のコマンドで検証してください:openssl s_client -connect yoursite.com:443 -cipher 'ECDHE'。接続に成功すれば、PFS は機能しています。PFS のもう一つの利点はコンプライアンスです。多くのフレームワーク(PCI-DSS、NIST)は、機密データの保護のために PFS を推奨または要求しています。欠点:DHE の演算は RSA よりも計算コストが高いため、わずかなパフォーマンスのオーバーヘッドがありますが、現代的なハードウェア(AES-NI、CPU の暗号アクセラレーション)では影響はごくわずかです。
