Apache 2.4系でHTTP/2サーバを構築してみるテスト。
サーバ負荷をRSAとECDSAで比較Let's Encryptを使ったECDSA証明書の取り方を書いたのですが、あまり人気がないみたいです。 [参考]>>ECDSA証明書の取り方(letsencrypt-auto)
[参考]>>ECDSA対応の証明書取得(dehydratedコマンド)
なんでだろうと考えてみたのですが、そういえば、ECDSAのメリットをきちんと説明したサイト自体があまり無いことに気づき、ECDSA自体を説明しなければいけないのでは?!と思った次第です。
ECDSA証明書の利点は、ずばりサーバ負荷の軽減にあります。
特に、RSA暗号や、ECDHE、ECDSAといった楕円曲線(ECC)系の暗号は、演算負荷が高く、これが処理時間を支配しています。 これらは、TLS接続を開始する際に、必要な演算です。以下、この接続開始の演算についてみていきます。
そのまえに、まずは、RSA暗号のフローをおさらいします。 RSA暗号で、証明書の認証と、鍵交換を行った場合、以下の暗号を処理することになります。 まず、クライアントは、サーバから受信した証明書について、①から⑧で証明書チェーンの妥当性を検証します。 具体的には、Issuer(発行者)とSubjectが一致しているか、ルート証明から、サーバ証明書まで、たどります。これを証明書チェーンの妥当性の検証と呼びます。 そして妥当性が検証できたら、⑨では、サーバ証明書のSubjectのCN(common name) と、サーバのDNS名が一致しているか,確認します。 続いて、鍵認証と鍵交換をおこないます。Forward Secrecy(FS:前方秘匿性)が実現できない代わりに、RSAでは、鍵認証と鍵交換を同時に行うことができます。 クライアントはプリマスタシークレットと呼ばれる秘密鍵の元を生成し、これを⑩で、公開鍵の情報を元に暗号化します。 これをサーバに送り、サーバは⑪でこれを秘密鍵の情報を元に復号化します。 RSA暗号の演算量は、公開鍵の情報をつかうのか、秘密鍵の情報を使うのかで、大きく異なります。 演算量は、公開鍵の情報を使うものが軽く、「検証(Verify)」と呼ばれる処理になります。 秘密鍵の情報を使うものは重く、「署名(Sign)」と呼ばれます。 RSA暗号にかかわる部分だけを見ると、証明書チェーンの検証(③と⑦)で2回、サーバにプリマスターシークレットを暗号化(⑩)するために、検証1回相当の演算を行います。 このように、クライアントは、合計、2048ビット換算で約50回程度の乗算と除算を行います。 サーバ、RSA暗号にかかわる演算は、⑪の1回ですが、ここで、秘密鍵を使った署名と同じ演算量の演算を行います。 これが、2048ビット換算で約2000回以上となります。つまりサーバは、クライアントの40倍以上の演算を行うことなります。これが、TLS通信を開始する際に、致命的にサーバ側の処理が重くなる原因です。
さて、この2048ビットのRSA演算は、いったいどれくらい時間がかかるものなのでしょうか? OpenSSLでは、以下のコマンドで、おおまかな性能を見ることができます。 2コア4スレッド、2.4GHzのXEONプロセッサで見てみましょう。 # /usr/local/ssl/bin/openssl speed rsa2048 Doing 2048 bit private rsa's for 10s: 1512 2048 bit private RSA's in 9.99s Doing 2048 bit public rsa's for 10s: 49257 2048 bit public RSA's in 10.00s sign verify sign/s verify/s rsa 2048 bits 0.006607s 0.000203s 151.4 4925.7 signが、秘密鍵を使ったRSA暗号の処理に相当します。 (verifyは公開鍵を使ったRSA暗号の1回の処理時間です。上記図のように、中間証明書1枚の場合、クライアントは、3回この演算を行います。)
つまり、サーバのCPUの能力を、全てTLS接続にかけると、最大で、1秒間に新たに151接続分のTLS接続を開始できることになります。 (実際は、TCP/IP処理や、その他HTTPリクエストの処理なども絡むので、この性能が出ることはありません。) これが、RSA暗号を使った、TLS通信開始の演算量になります。 この数字は、システム全体のスループットなので、実際にかかる時間は、並列数を掛けた時間になります。 このサーバは、4スレッドが並行して処理されているので、1つのスレッドに注目して見れば、この4倍の処理時間となります。 2048ビットのRSA暗号を使うと、サーバ側では、TLS接続を開始するために、最低、6.6ms×4=26.4msの時間が必要なることがわかります。
ECDSAのサーバ演算量これに対して、ECDSAは、どうでしょう。 Let's EncryptのECDSA証明書で説明してみます。 暗号強度は、RSA暗号の公開鍵3072ビットに相当する、256ビットの楕円曲線暗号で見てみましょう。 まず、⓪でサーバ側が、ECDHE用のECC(楕円曲線暗号)パラメータを生成し、これをECDSA署名します。 このパラメータは、ECDSAとは別の楕円曲線で、ECDHE用になります。 これを、中間証明書・サーバ証明書とともに、クライアントに送ります。これはRSAと同じです。 続いて、RSAと同様にクライアントは、サーバから受信した証明書について、①から⑧で証明書チェーンの妥当性を検証します。サーバ証明書が、ECDSAだからといって、全ての証明書チェーンがECDSAで署名されている必要は無く、証明書チェーンにRSAが含まれていても問題ありません。 証明書の公開鍵がRSAならば、RSAで行われます。証明書チェーンは妥当性のチェックが、演算量が少ないRSAの「検証」で済むのは好ましいですね。 後述しますが、256ビットのECDSAの検証は、2048ビットのRSAに比べると重いです。
⑨では、サーバ証明書のSubjectのCN(common name) とDNS名が一致しているか,確認します。 続いて、鍵認証と鍵交換をおこないます。RSAと違い、ここは、鍵認証でECDSAを、鍵交換で、ECDHEを使います。 クライアントは、⑩⑪で、サーバ側のECCパラメータが、サーバ証明書の公開鍵で、ECDSA署名されたものか、検証します。 RSA鍵認証では、クライアントが生成したプリマスタシークレットを使って認証してたものが、サーバが生成したECCパラメータで認証するように変わったのが、大きな違いですね。 確認ができれば、クライアントは、クライアント側のECCパラメータをサーバに送ります。 それぞれのECCパラメータを使って、クライアントは⑫で、サーバ⑬で、ECDHE鍵交換を行います。
それでは、実際の性能を先ほどと同じ環境で試します。 使用する楕円曲線は、ポピュラーな、256ビットの楕円曲線で比較してみます。(256ビットの楕円曲線暗号は、3072ビットのRSA暗号に相当するといわれています。) # /usr/local/ssl/bin/openssl speed rsa2048 ecdsap256 Doing 2048 bit private rsa's for 10s: 1512 2048 bit private RSA's in 9.99s Doing 2048 bit public rsa's for 10s: 49257 2048 bit public RSA's in 10.00s Doing 256 bit sign ecdsa's for 10s: 27473 256 bit ECDSA signs in 10.00s Doing 256 bit verify ecdsa's for 10s: 7145 256 bit ECDSA verify in 10.00s sign verify sign/s verify/s rsa 2048 bits 0.006607s 0.000203s 151.4 4925.7 sign verify sign/s verify/s 256 bit ecdsa (nistp256) 0.0004s 0.0014s 2747.3 714.5
ここから、 RSAは、秘密鍵の処理(ここでの署名)が非常に重く、公開鍵の処理(ここでの検証)がECDSAより軽い。 ECDSAは、署名の処理がRSAの16倍速いが、検証が、RSAの7倍遅い。 ECDSAは、署名と検証の演算量のバランスが、RSAほど悪くない。 つまり、 RSAは、検証の機会が多い、証明書に対する署名に向いており、 ECDSAは鍵認証の証明書として使えば、クライアントとサーバの演算量のバランスが良くなる、 といえます。
さきほどの結果から、接続開始するために、必要な演算量を見てみます。 まず、256ビットのECDSAの鍵認証の署名(⓪)は、0.4msの時間がかかることがわかります。 ただしこれは、鍵認証にかかる時間で鍵交換を含みません。ECDHE鍵交換(⑬)をふくめると、 # /usr/local/ssl/bin/openssl speed ecdhp256 Doing 256 bit ecdh's for 10s: 8563 256-bit ECDH ops in 10.00s op op/s 256 bit ecdh (nistp256) 0.0012s 856.3
なので、スループット時間で見ると、ECDHE+ECDSA=1.2[ms]+0.4[ms]=1.6[ms]となります。 これでも、1秒間に最大600接続のTLS接続が開始できるので、RSAの151接続より、大幅に改善できます。 このように、ECDHE+ECDSAはサーバの負荷を軽くしてくれそうです。
クライアントの演算量を含めた、RSAとECDSAの比較さて、クライアントとサーバの両方を合わせたシステム全体ではどうなるでしょうか? 比較をする前に、条件をそろえます。
比較は、スループットの処理時間で行います。 RSAでは、Forward Secrecy(FS:前方秘匿性)を確保できないのに比べ、ECDHE+ECDSAはFSを実現しています。 そこで、ここでの比較では、FSを確保した、ECDHE+RSAの組み合わせを含めて検証します。 ちなみに、2016年2月現在、FacebookおよびGoogleがECDHE+ECDSAを利用し、TwitterがECDHE+RSAを使用しています。
これまで調べたとおり、サーバ側の公開鍵暗号に係わる処理時間は、
RSAだと、6.6[ms]です。 Twitterが使っているECDHE+RSAだと、6.6[ms]+1.2[ms]=7.8[ms]となります。 これに対して、ECDHE+ECDSAだと0.4[ms]+1.2[ms]=1.6[ms]になります。
このように、、サーバ側からみるとECDHE+ECDSAはいいことだらけですが、続いてクライアントの処理をみてみます。
クライアントもサーバと同じスペックだとして、RSAだと、 0.2[ms]×3(③と⑦と⑩)=0.6[ms] かかりますが、ECDHE+ECDSAだと、 0.2[ms]×2(③と⑦)+1.4[ms](⑩)+1.2[ms](⑫)=3.0[ms] と、5倍になります(サーバよりも重い処理?!)。 ただ、FSを実現した、ECDHE+RSAだと、 0.2[ms]×3(③と⑦と⑩)=0.6[ms]に、ECDHE鍵交換の1.2[ms](⑫)を加えて1.8[ms] となります。こちらを基準とすれば、約1.5倍程度なので、サーバ側の利点を考えれば、目くじらを立てるほどのことはないと思います。 Twitterは、クライアントの負荷を重視し、FacebookおよびGoogleは、サーバの負荷を重視しているということかもしれません。
またLet's Encryptの、ルート証明書と、中間証明書がRSA署名なので、この点、クライアントの署名検証スピードに貢献しています。 まとめると、TLS接続を開始するための演算時間は、以下の表のとおりです。 (あくまで、処理の断片を切り取った参考値としてご覧ください。実際は、雑多な処理が加わるので、もう少しかかります。) 下の表は、スループットの処理時間の比較です。
これを図で比較すると以下のようになります。
ECDHEやAESのビット数を考慮した試算については以下をご覧ください。 この際、クライアントとサーバの処理時間の合計が短くなることは、TATの短縮および、サービス全体の消費電力が減ることになるので、昨今の電力事情を考えると、この視点が大切ではないでしょうか。 そして、サーバが処理できる接続数が増えることは、大規模サイトにとっては、なにより必要なことです。 スマホやPC相手のサービスなら、いますぐ256ビットのECDSA証明書を試してみて損は無いのではないでしょうか。
OpenSSL 1.1.0系では楕円曲線暗号の処理が改善
しています。
OpenSSL 1.1.0系では楕円曲線暗号、特にprime256v1と呼ばれる公開鍵長が256ビットの処理が改善
しています。どの程度、改善しているかは、以下のページでご確認ください。
[参考]>>OpenSSL 1.1.0のベンチマーク
自動スケールするクラウドをお使いの場合は、費用の節約につながることは間違いありません。 ちなみに、当サイトはECDSAとRSAの2種類の証明書に対応していますが、2016年度は以下のようなアクセスとなりました。 ということで、下記リンクから、Let's EncryptのECDSA対応のサーバ証明書を取得してみてください。
|