SSLで使う暗号スイートはブラウザに任せていいのでは(※正しい設定なら)

# ……たぶん

自信がないわけではないのですが、私が知っている情報が古い可能性もあるので、誤りがあるようでしたらぜひご指摘ください。

また、本文で特に断りがない場合、SSLという単語は **SSL/TLS** を指します。

# 暗号スイート(cipher suite)の“選ばせ”方

Apache httpd の設定を解説した記事では、多くの場合
`SSLHonorCipherOrder` を `on` にすることが推奨されています。こうするとサーバで暗号スイートを指定するのでダウングレード攻撃を防げる、という理由からです。

でもこれはベストプラクティスと言えるでしょうか？
私は、サーバが提示する暗号スイートが **すべて安全なもの** なら、サーバで指定するのではなく、クライアント(ブラウザ)に選ばせた方がいい、と考えています。

# 暗号スイートの提示

安全な暗号スイートとはなにか、というのはサーバの運用次第だと思います。ここでは、2020年6月3日時点で安全な、たいていのクライアントが対応し、処理速度の速いものを選びました。

Apache httpd で、利用できる暗号スイートを以下のように設定します。

```apache
SSLCipherSuite ECDSA+AESGCM
```

このとき、クライアントに提示される暗号スイートは以下のようになります。

```
TLS_AES_256_GCM_SHA384  TLSv1.3 Kx=any      Au=any  Enc=AESGCM(256) Mac=AEAD
TLS_CHACHA20_POLY1305_SHA256 TLSv1.3 Kx=any      Au=any  Enc=CHACHA20/POLY1305(256) Mac=AEAD
TLS_AES_128_GCM_SHA256  TLSv1.3 Kx=any      Au=any  Enc=AESGCM(128) Mac=AEAD
ECDHE-ECDSA-AES256-GCM-SHA384 TLSv1.2 Kx=ECDH     Au=ECDSA Enc=AESGCM(256) Mac=AEAD
ECDHE-ECDSA-AES128-GCM-SHA256 TLSv1.2 Kx=ECDH     Au=ECDSA Enc=AESGCM(128) Mac=AEAD
```

先頭3行は TLS 1.3 のものです。TLS 1.3 に対応していない場合は下の2行だけが出てくるはずです。

# どの暗号スイートが使われるのか

この5つのうち、実際にどれが使われるのかを SSLLabs でチェックします[^ssllabs]。

[^ssllabs]: SSLLabs はあくまでシミュレーションしているだけなので、クライアントが実際に使う暗号スイートは別のこともあります。というか、別のものが見つかりました(後述)

## サーバで指定する場合

サーバで暗号スイートを **指定する** 場合、httpd を次のように設定します。

```apache
SSLHonorCipherOrder on
```

この場合、クライアントに提示されるリストは以下のようになります。クライアントはこのリストから、サポートしている暗号スイートの中で最も上位のものを使う必要があります。
<img width="939" alt="ssl_server_preferred.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223758/c449637a-b330-4ba3-76da-8df3933ca9a5.png">

そうすると、各クライアントは次のように選びます。
![ssl_handshake_server_preferred.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223758/13685266-6420-2afa-2899-136b9329a003.png)

最近のほとんどのクライアントは、`TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384` をサポートしているため、これを使います。TLS 1.3 に対応するクライアントは `TLS_AES_256_GCM_SHA384` を選ぶことになります。

## サーバで指定しない場合

サーバで暗号スイートを **指定しない** 場合、httpd を次のように設定します。

```apache
SSLHonorCipherOrder off
```

この場合、クライアントはこの中から自由に暗号スイートを自由に選んで使うことができます。(リストの中身は、サーバで指定する/しないにかかわらず同じです)
<img width="946" alt="ssl_server_no_preferred.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223758/8c3a9c9e-994c-eb34-a620-275ea96805c5.png">

この違いがどう影響するでしょうか？
各クライアントは次のように選びます。
![ssl_handshake_no_server_preferred.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223758/2b37a6c0-f6c1-67d1-3bfe-a6d07fb78d9f.png)

どうでしょうか。自由に選べるので、クライアントによって選ぶ暗号スイートが違っています。

特によく見てもらいたいのですが、サーバで暗号スイートを指定したときには`TLS_AES_256_GCM_SHA384` を使っていた Chrome や Firefox が `TLS_AES_128_GCM_SHA256` を選んでいるところです。

これは、**Chrome や Firefox は、現時点では暗号強度は128ビットでも問題ない**、と考えているのが分かります。おそらくパフォーマンスとのバランスを考えた上での結果でしょうが、他にも意図があるのかもしれません。これは各クライアントの戦略の一つなのだと思います。

# サーバで指定することの問題点

冒頭でも書きましたが、サーバで暗号スイートを指定するのはダウングレード攻撃を招く、という説明が多く見られます。

上であげた5つの暗号スイートは、アルゴリズム自体にも、openssl の実装にも、現時点(2019/6/3時点)で問題は見つかっていませんが、あくまで **現時点の話** です。

この中の一つに脆弱性が見つかったとします。それがゼロデイ攻撃で使われた場合、クライアントが暗号スイートを選べることを悪用される恐れがあります。
しかしそれがゼロデイ攻撃に繋がらないのなら、サーバ管理者が、見つかった脆弱性を取り除けばいいのです。

またサーバが暗号スイートを指定するようにしても、その暗号スイートに脆弱性が見つかれば、同じ問題が発生します。

# 結論

各クライアントにはパフォーマンスと安全性、その他の戦略があります。
また、サーバで暗号スイートを指定することも必ずしも安全とはいえません。

であれば、暗号スイートは **サーバで指定せず、クライアントに選ぶ自由を与えて、各々の戦略を活かしてもらった方がいいのではないか**、と私は思うのです。

# 参照

この記事を作るにあたって調べたものを、以下にまとめました。

## 1. 各クライアントの優先順位

サーバで暗号スイートの優先度を指定しないときに、クライアント(ブラウザ)が選ぶときの優先順位を実際に調べてみました。

**Firefox 78** : Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:76.0) Gecko/20100101 Firefox/76.0
<img width="902" alt="ssl_b_firefox.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223758/ba0fc294-6d46-dbe5-7450-e1aea595a3ac.png">

**Chrome 83** : Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.61 Safari/537.36
<img width="892" alt="ssl_b_chrome.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223758/6693e37c-bc73-4dd3-d4fe-2a19c3a33178.png">


**Safari 13.1.1** : Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_5) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.1.1 Safari/605.1.15
<img width="893" alt="ssl_b_safari.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223758/1a14811e-f8a3-c65c-61b9-c826c05daa31.png">

**curl 7.70.0**
<img width="896" alt="ssl_b_curl.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223758/35ed07e8-3488-b5e3-2b66-af9ae9f31835.png">

……思ったより違いが出なかった、というか `TLS_AES_128_GCM_SHA256` が1番目になっているのが共通で戸惑っているんですが[^curl]、2番目以降はそれぞれのクライアントで違うのが分かります。

[^curl]: なので苦し紛れに curl の結果を混ぜてみました……！

TLS 1.3 では `TLS_CHACHA20_POLY1305_SHA256` (ChaCha20-Poly1305) が選ばれることがあるようです。これはAESをハードウェアで処理できない環境に向いている、とされます。クライアントは、こういった環境の違いを加味して優先順位を変えているかもしれません。(ChaCha20-Poly1305 については記事末尾を参照してください)



## 2. ChaCha20-Poly1305 について

以下の情報は2016年と2017年のもので、AES が至るところで使われている現在(2020年)では、おそらく状況は大きく変わっているでしょう。

* [新しいTLSの暗号方式ChaCha20-Poly1305 - ぼちぼち日記](https://jovi0608.hatenablog.com/entry/20160404/1459748671)

上のページでは、ChaCha20-Poly1305 の登場理由として

- AES が TLS の SPOF になっている
    - 現在実用的に広く使えるTLSの対称暗号が実質AESの一択しかない
- AES-NI のない ARM 環境でも安全で高速な暗号が欲しい
    - AES はハードウェア処理(AES-NI)が使える環境では非常に高性能だが、ソフトウェア処理だけではそれほど性能がでない

を挙げています。

* [ChaCha20-Poly1305の実装性能調査 - 株式会社レピダム](https://www.cryptrec.go.jp/exreport/cryptrec-ex-2702-2017.pdf) (PDF)<br>
<img width="727" alt="chacha_spped.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223758/37ed5ab4-4ce2-334a-4938-cc0b9fc5391d.png">

ChaCha20-Poly1305 は、AES のハードウェア処理を無効にした状態ではAES-GCM に比べて高速ということを実験した結果です。(AES-CCMは ブラウザが対応していない)