> Source: https://www.ymotongpoo.com/works/goblog-ja/tls-cipher-suites/


# crypto/tlsにおける暗号スイート順序の自動化

[Automatic cipher suite ordering in crypto/tls](https://go.dev/blog/tls-cipher-suites) by Filippo Valsorda

Goの標準ライブラリはcrypto/tlsを提供しています。これはTransport Layer Security (TLS) の堅牢な実装です。TLSはインターネットにおける最も重要なセキュリティプロトコルであり、HTTPSの基盤となるコンポーネントです。Go 1.17では、暗号スイートの優先順位を自動化することで、その設定をより簡単に、より安全に、そしてより効率的にしました。

## 暗号スイートの仕組み

暗号スイートの起源はTLSの前身であるSecure Socket Layer (SSL) にまで遡ります。SSLでは暗号スイートは[「cipher kinds(暗号の種類)」と呼ばれていました](https://datatracker.ietf.org/doc/html/draft-hickman-netscape-ssl-00#appendix-C.4)。暗号スイートは `TLS_RSA_WITH_AES_256_CBC_SHA` や `TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256` のような、一見ものものしい識別子で、TLSコネクションにおいて鍵交換、証明書の認証、そしてレコードの暗号化に使われるアルゴリズムを綴ったものです。

暗号スイートはTLSハンドシェイクの間に _ネゴシエーション_ されます。クライアントは最初のメッセージであるClient Helloの中で自身がサポートする暗号スイートの一覧を送信し、サーバーはその一覧の中から1つを選んでクライアントに選択結果を伝えます。クライアントは自身が優先する順序でサポートする暗号スイートの一覧を送信しますが、サーバーはその中から好きなように選んで構いません。最も一般的には、サーバーは自身の設定に応じて、クライアントの優先順序かサーバーの優先順序のいずれかで、双方がサポートする最初の暗号スイートを選択します。

暗号スイートは実のところネゴシエーションされる数あるパラメータの1つに過ぎません(サポートする曲線(グループ)や署名アルゴリズムも、それぞれの拡張を通じて別途ネゴシエーションされます)。ですが暗号スイートは最も複雑で有名なものであり、長年にわたって開発者や管理者が意見を持つよう訓練されてきた唯一のものでもあります。

TLS 1.0から1.2では、これらすべてのパラメータが複雑な依存関係の網の中で相互に影響し合っています。たとえば、サポートする証明書はサポートする署名アルゴリズム、サポートする曲線、そしてサポートする暗号スイートに依存します。TLS 1.3ではこれが劇的に単純化されました。暗号スイートは対称暗号アルゴリズムのみを指定し、サポートする曲線(グループ)が鍵交換を司り、サポートする署名アルゴリズムが証明書に適用されます。

## 開発者に丸投げされた複雑な選択

ほとんどのHTTPSサーバーやTLSサーバーは、暗号スイートの選択とその優先順序をサーバーの運用者やアプリケーションの開発者に委ねています。これは、さまざまな理由から最新かつ専門的な知識を必要とする複雑な選択です。

古い暗号スイートの中には安全でないコンポーネントを含むものもあれば、安全であるために極めて注意深く複雑な実装を必要とするものもあり、さらにはクライアントが特定の緩和策を適用しているか、特定のハードウェアを持っている場合にのみ安全なものもあります。個々のコンポーネントの安全性だけでなく、暗号スイートによってコネクション全体のセキュリティ特性は大きく異なります。というのも、ECDHEやDHEを使わない暗号スイートはフォワードシークレシー(証明書の鍵を使って後から、あるいは受動的にコネクションを復号できないという性質)を提供しないからです。最後に、サポートする暗号スイートの選択は互換性とパフォーマンスにも影響します。エコシステムについての最新の理解を持たずに変更を行うと、レガシーなクライアントとの接続を壊してしまったり、サーバーが消費するリソースを増やしてしまったり、モバイルクライアントのバッテリーを消耗させてしまったりしかねません。

この選択はあまりに難解で繊細なため、たとえば優れた[Mozilla SSL Configuration Generator](https://ssl-config.mozilla.org/)のような、運用者を手引きするための専用ツールまで存在するほどです。

なぜこのような状況になったのでしょうか。そしてなぜこのような形になっているのでしょうか。

まず、個々の暗号コンポーネントは以前はもっと頻繁に破られていました。2011年、BEAST攻撃がCBC暗号スイートを破ったときには、その対策をクライアント側でしかとれない形だったため、サーバー側は影響を受けないRC4を優先するように移行しました。2013年になってRC4も破られていることが明らかになると、サーバーは今度はCBCに回帰しました。Lucky Thirteen攻撃によって、MAC-then-encryptという逆順の設計のためにCBC暗号スイートを実装するのが極めて困難であることが明らかになったとき……そう、他に選択肢がなかったので、実装者たちは[慎重に策を弄して](https://www.imperialviolet.org/2013/02/04/luckythirteen.html)CBCを実装せざるを得ず、その困難な作業に何年も[失敗し続けました](https://blog.cloudflare.com/yet-another-padding-oracle-in-openssl-cbc-ciphersuites/)。設定可能な暗号スイートと[暗号の俊敏性(cryptographic agility)](https://www.imperialviolet.org/2016/05/16/agility.html)は、あるコンポーネントが破られたときにその場で置き換えられるという、ある種の安心感をかつては与えてくれていました。

現代の暗号技術はこれとは大きく異なります。プロトコルが時々破られることは今でもありますが、抽象化された個々のコンポーネントが破られることは滅多にありません。 _2008年のTLS 1.2から導入されたAEADベースの暗号スイートは、これまで1つも破られていません。_ 今日では、暗号の俊敏性はむしろ負債です。それは弱点やダウングレードにつながりかねない複雑性をもたらすものであり、必要となるのはパフォーマンスやコンプライアンス上の理由に限られます。

パッチの当て方もかつては違いました。今日では、公開された脆弱性に対して速やかにソフトウェアパッチを適用することが安全なソフトウェア運用の要であると認識されていますが、10年前はそれが標準的な慣行ではありませんでした。脆弱な暗号スイートに対応するには設定変更のほうがずっと速い選択肢だと見なされていたため、運用者が設定を通じてそれらを完全に管理する立場に置かれていました。現在では逆の問題を抱えています。パッチが完全に適用され最新化されたサーバーであっても、設定が何年も手つかずのままであるために、奇妙な、あるいは最適とは言えない、もしくは安全ではない振る舞いをしてしまうことがあるのです。

最後に、サーバーはクライアントよりも更新が遅い傾向にあり、それゆえ最良の暗号スイートを判断する上ではあまり信頼できないと理解されていました。しかし、暗号スイートの選択について最終的な決定権を持つのはサーバー側です。そのため、サーバーが強い意見を持つのではなく、クライアントの優先順序に従うことがデフォルトとなりました。これは今でもある程度は正しいことです。ブラウザは自動更新の仕組みをうまく実現し、平均的なサーバーよりもずっと最新の状態を保っています。その一方で、数多くのレガシーデバイスは今やサポートが終了し、古いTLSクライアント設定のまま取り残されています。そのため、最新化されたサーバーのほうが一部のクライアントよりも適切に選択できることがしばしばあります。

どのような経緯であれ、アプリケーション開発者やサーバー運用者に暗号スイート選択の機微に通じた専門家になることや、設定を最新に保つために最新の動向を追い続けることを求めるのは、暗号技術エンジニアリングの失敗です。彼らが私たちのセキュリティパッチを適用してくれさえすれば、それで十分であるべきです。

Mozilla SSL Configuration Generatorは素晴らしいツールです。そして、それは本来存在するべきではないのです。

この状況は改善しつつあるのでしょうか。

ここ数年の傾向には良いニュースと悪いニュースがあります。悪いニュースは、優先順序の決定がさらに微妙になっているということです。というのも、同等のセキュリティ特性を持つ暗号スイートの集合が存在するからです。そうした集合の中でどれが最良かは利用可能なハードウェアに依存し、設定ファイルで表現するのが難しくなっています。他のシステムでは、もともと単純な暗号スイートの一覧だったものが、今では[より複雑な構文](https://boringssl.googlesource.com/boringssl/+/c3b373bf4f4b2e2fba2578d1d5b5fe04e410f7cb/include/openssl/ssl.h#1457)や `SSL_OP_PRIORITIZE_CHACHA` のような[追加のフラグ](https://www.openssl.org/docs/man1.1.1/man3/SSL_CTX_clear_options.html#:~:text=session-,ssl_op_prioritize_chacha,-When)に依存するようになっています。

良いニュースは、TLS 1.3が暗号スイートを劇的に単純化し、TLS 1.0から1.2とは重ならない独立した集合を使っていることです。TLS 1.3の暗号スイートはすべて安全なので、アプリケーション開発者やサーバー運用者はそれらについて一切気にする必要がありません。実際、BoringSSLやGoの `crypto/tls` のようないくつかのTLSライブラリでは、TLS 1.3の暗号スイートを設定すること自体が一切できないようになっています。

## Goのcrypto/tlsと暗号スイート

Goでは、TLS 1.0から1.2の暗号スイートを設定できるようになっています。アプリケーションは以前から[`Config.CipherSuites`](https://pkg.go.dev/crypto/tls#Config.CipherSuites)によって有効にする暗号スイートとその優先順序を設定できました。[`Config.PreferServerCipherSuites`](https://pkg.go.dev/crypto/tls#Config.PreferServerCipherSuites)が設定されていない限り、サーバーはデフォルトでクライアントの優先順序を優先します。

Go 1.12でTLS 1.3を実装した際、[TLS 1.3の暗号スイートは設定可能にしませんでした](https://go.dev/issue/29349)。TLS 1.3の暗号スイートはTLS 1.0から1.2のものとは独立した集合であり、何よりすべてが安全であるため、選択をアプリケーションに委ねる必要がないからです。`Config.PreferServerCipherSuites` は依然としてどちら側の優先順序が使われるかを制御しており、ローカル側の優先順序は利用可能なハードウェアに依存します。

Go 1.14では[サポートする暗号スイートを公開しました](https://pkg.go.dev/crypto/tls#CipherSuites)が、意図的に中立な順序(IDでソートされた順序)で返すようにしました。優先順位のロジックを静的なソート順序で表現することに縛られてしまわないようにするためです。

Go 1.16では、クライアントまたはサーバーのどちらかがAES-GCM用のハードウェアサポートを欠いていることを検知した場合に、サーバー側で[ChaCha20Poly1305の暗号スイートをAES-GCMよりも積極的に優先する](https://go.dev/cl/262857)ようになりました。これは、AES-GCMを効率的かつ安全に実装するには専用のハードウェアサポート(AES-NIやCLMULといった命令セットなど)が必要になるためです。

**先ごろリリースされたGo 1.17では、すべてのGoユーザーに対して暗号スイートの優先順序の決定を引き継ぐことになりました。** `Config.CipherSuites` は引き続きどのTLS 1.0から1.2の暗号スイートを有効にするかを制御しますが、順序付けには使われなくなり、`Config.PreferServerCipherSuites` も現在では無視されます。代わりに、利用可能な暗号スイート、ローカルのハードウェア、そして推測されたリモート側のハードウェア性能に基づいて、`crypto/tls` が[すべての順序付けの決定を行います](https://go.dev/cl/314609)。

[現在のTLS 1.0から1.2の順序付けロジック](https://cs.opensource.google/go/go/+/9d0819b27ca248f9949e7cf6bf7cb9fe7cf574e8:src/crypto/tls/cipher_suites.go;l=206-270)は次のルールに従います。

1. ECDHEは静的なRSA鍵交換よりも優先されます。

   暗号スイートの最も重要な性質は、フォワードシークレシーを実現することです。私たちは「古典的な」有限体Diffie-Hellman鍵交換を実装していません。これは複雑で、遅く、より弱く、そしてTLS 1.0から1.2では[巧妙に破られている](https://datatracker.ietf.org/doc/draft-bartle-tls-deprecate-ffdh/)からです。つまり、レガシーな静的RSA鍵交換よりも楕円曲線Diffie-Hellman鍵交換を優先するということです。(後者は単純にコネクションの秘密情報を証明書の公開鍵で暗号化するだけなので、将来証明書が漏洩した場合に復号できてしまいます。)

2. 暗号化にはAEADモードがCBCよりも優先されます。

   Lucky13に対する部分的な対策([2015年に私が初めてGo標準ライブラリに行ったコントリビューションです！](https://go.dev/cl/18130))を実装してはいるものの、CBCスイートを正しく実装するのは[悪夢のようなもの](https://blog.cloudflare.com/yet-another-padding-oracle-in-openssl-cbc-ciphersuites/)です。そのため、他のより重要な要素が等しい場合には、代わりにAES-GCMとChaCha20Poly1305を選びます。

3. 3DES、CBC-SHA256、RC4は他に何も利用できない場合に限り、この優先順位で使われます。

   3DESは64ビットブロックを使うため、十分な量のトラフィックがあれば根本的に[バースデー攻撃](https://sweet32.info)に対して脆弱です。3DESは[`InsecureCipherSuites`](https://pkg.go.dev/crypto/tls#InsecureCipherSuites)に分類されていますが、互換性のためにデフォルトで有効になっています。(優先順序を制御することのもう1つの利点は、より安全性の低い暗号スイートをデフォルトで有効にしておいても、アプリケーションやクライアントが最後の手段としてしか選ばないことを心配しなくてよくなる点です。より弱い暗号スイートが利用可能であることを利用して、より良い代替手段をサポートする相手を攻撃するようなダウングレード攻撃は存在しないため、これは安全です。)

   CBC暗号スイートはLucky13型のサイドチャネル攻撃に対して脆弱であり、前述の複雑な対策は[SHA-1ハッシュに対してのみ部分的に実装しており](https://www.imperialviolet.org/2013/02/04/luckythirteen.html)、SHA-256に対しては実装していません。CBC-SHA1スイートには、その追加の複雑さを正当化するだけの互換性上の価値がありますが、CBC-SHA256にはそれがないため、デフォルトでは無効になっています。

   RC4には、サイドチャネルなしに平文の復元につながりかねない[実用的に悪用可能な偏り](https://www.rc4nomore.com)があります。これ以上悪いことはないので、RC4はデフォルトで無効になっています。

4. 暗号化には、双方がAES-GCM用のハードウェアサポートを持っていない限り、ChaCha20Poly1305がAES-GCMよりも優先されます。

   前述の通り、AES-GCMをハードウェアサポートなしに効率的かつ安全に実装するのは困難です。ローカルにハードウェアサポートがないと判断した場合、あるいは(サーバー側では)クライアントがAES-GCMを優先していないと判断した場合には、代わりにChaCha20Poly1305を選びます。

5. 暗号化にはAES-128がAES-256よりも優先されます。

   AES-256はAES-128よりも大きな鍵を持ち、それは通常望ましいことですが、コアとなる暗号化関数のラウンド数もより多くなるため、より低速になります。(AES-256における追加のラウンドは鍵長の変更とは独立したものであり、暗号解析に対してより広い安全マージンを提供しようとする試みです。) より大きな鍵が役立つのはマルチユーザーやポスト量子暗号の文脈においてのみですが、これらはTLSには関係ありません。TLSは十分にランダムなIVを生成しますし、ポスト量子鍵交換のサポートも持っていないからです。大きな鍵に何のメリットもない以上、私たちは速度を優先してAES-128を選びます。

[TLS 1.3の順序付けロジック](https://cs.opensource.google/go/go/+/9d0819b27ca248f9949e7cf6bf7cb9fe7cf574e8:src/crypto/tls/cipher_suites.go;l=342-355)は最後の2つのルールのみを必要とします。TLS 1.3は、最初の3つのルールが守ろうとしていた問題のあるアルゴリズムをすべて排除しているからです。

## よくある質問

_ある暗号スイートが破られていることが判明したらどうなりますか?_ 他のあらゆる脆弱性と同様に、サポートされているすべてのGoのバージョンに対するセキュリティリリースで修正されます。すべてのアプリケーションは、安全に運用するためにセキュリティ修正を適用できるよう準備しておく必要があります。歴史的に見て、暗号スイートが破られることはますます稀になっています。

_なぜTLS 1.0から1.2で有効にする暗号スイートは設定可能なままにしているのですか?_ どの暗号スイートを有効にするかを選ぶ際には、ベースラインとなる安全性とレガシーとの互換性の間に意味のあるトレードオフがあります。これは、エコシステムの許容できない一部を切り捨ててしまうか、あるいは最新のユーザーの安全性の保証を下げてしまうかのどちらかを避けて、私たちだけで決めることはできない選択です。

_なぜTLS 1.3の暗号スイートは設定可能にしないのですか?_ 逆に、TLS 1.3ではトレードオフを取るべき要素がありません。TLS 1.3のすべての暗号スイートが強力な安全性を提供するからです。そのため、すべてを有効にしたままにしておき、開発者の関与を必要とせずにコネクションの詳細に基づいて最も高速なものを選ぶことができます。

## まとめ

Go 1.17から、利用可能な暗号スイートが選択される順序は `crypto/tls` が引き継ぐことになりました。定期的に更新されたGoのバージョンを使っていれば、これは潜在的に古くなっているかもしれないクライアントに順序を決めさせるよりも安全であり、パフォーマンスの最適化を可能にし、Goの開発者からかなりの複雑さを取り除いてくれます。

これは、暗号に関する決定を開発者に委ねるのではなく、可能な限り私たち自身で行うという全体的な方針、そして私たちの[暗号技術の原則](https://go.dev/design/cryptography-principles)と一致するものです。他のTLSライブラリも同様の変更を採り入れ、繊細な暗号スイートの設定を過去のものにしてくれることを願っています。

By Filippo Valsorda

