剑客
关注科技互联网

Netty SSL性能调优

嗯,这篇不长的文章,是一个晚上工作到三点的血泪加班史总结而成。多一个读,可能就少一个人加班。

《 TLS协议分析 与 现代加密通信协议设计》
首先要感谢这篇文章,如果没有它,我可能还要花更多的时间才能完成。文章有点长,能看多少是多少,每句都是收获。

1.背景知识

1.1 协议史

1996: SSL3.0. 写成RFC,开始流行。目前(2015年)已经不安全,必须禁用。

1999: TLS1.0. 互联网标准化组织ISOC接替NetScape公司,发布了SSL的升级版TLS 1.0版。

2006: TLS1.1. 作为 RFC 4346 发布。主要fix了CBC模式相关的如BEAST攻击等漏洞。

2008: TLS1.2. 作为RFC 5246发布 。增进安全性,目前的主流版本。

2015之后: TLS 1.3,还在制订中。

1.2 TLS算法组合

在TLS中,5类算法组合在一起,称为一个CipherSuite:

  • 认证算法
  • 加密算法
  • 消息认证码算法 简称MAC
  • 密钥交换算法
  • 密钥衍生算法

比较常见的算法组合是 TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA 和  TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, 都是ECDHE 做密钥交换,使用RSA做认证,SHA256做PRF算法。

一个使用AES128-CBC做加密算法,用HMAC做MAC。

一个使用AES128-GCM做加密算法,MAC由于GCM作为一种AEAD模式并不需要。

两者的差别,在于 Block Cipher+HMAC 类的算法都爆出了各种漏洞,下一代的TLS v1.3干脆只保留了Authenticated-Encryption 类的算法,主要就是AES-GCM,AEAD模式(Authenticated-Encryption With Addtional data)里Encrypt和MAC直接集成为一个算法,在算法内部解决好安全问题。

1.3 Java 对SSL的支持

JDK7的client端只支持TLS1.0,服务端则支持TLS1.2。

JDK8完全支持TLS1.2。

JDK7不支持GCM算法。

JDK8支持GCM算法,但性能极差极差极差,按Netty的说法:

  •  Java 8u60以前多版本,只能处理1 MB/s。
  •  Java 8u60 开始,10倍的性能提升,10-20 MB/s。
  •  但比起 OpenSSL的 ~200 MB/s,还差着一个量级。

1.4 Netty 对SSL的支持

Netty既支持JDK SSL,也支持Google的boringssl, 这是OpenSSL 的一个fork,更少的代码,更多的功能。

依赖netty-tcnative-boringssl-static-linux-x86_64.jar即可,它里面已包含了相关的so文件,再也不用管Linux里装没装OpenSSL,OpenSSL啥版本了。

2. 性能问题的出现及调优

2.1 性能问题的出现

忘掉前面所有的背景知识,重新来到问题现场:

JDK7的JMeter HTTPS客户端,连接JDK8的Netty服务端时,速度还可以。

JDK8的JMeter HTTPS客户端,则非常慢,非常慢,非常吃客户端的CPU。

按套路,在JMeter端增加启动参数 -Djavax.net.debug=ssl,handshake  debug 握手过程。

(OpenSSL那边这个参数加了没用)

*** ClientHello, TLSv1.2,可以看到,Client端先发起协商,带了一堆可选协议

Cipher Suites: [TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256, TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256, TLS_RSA_WITH_AES_128_CBC_SHA256…]

*** ServerHello, TLSv1.2 然后服务端回选定一个

Cipher Suite: TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256

还可以看到,传输同样的数据,不同客户端/服务端组合下有不同的纪录:

Client: JDK7 JDK SSL + Server: JDK7/8 JDK SSL

**TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA

WRITE: TLSv1 Application Data, length = 32

WRITE: TLSv1 Application Data, length = 304

READ:  TLSv1 Application Data, length = 32

READ:  TLSv1 Application Data, length = 96

READ:  TLSv1 Application Data, length = 32

READ:  TLSv1 Application Data, length = 10336

Client: JDK8 JDK SSL + Server: JDK8 Open SSL

** TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256

Thread Group 1-1, WRITE: TLSv1.2 Application Data, length = 300

Thread Group 1-1, READ: TLSv1.2 Application Data, length = 92

Thread Group 1-1, READ: TLSv1.2 Application Data, length = 10337

2.2 原因分析

带着上面的记录,经过一晚的奋战,得出了文章一开始的背景信息,再回头分析就很好理解了,JMeter Https 用的是JDK8 SSL,很不幸的和服务端的OpenSSL协商出一个JDK8实现超慢的TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256。

对于服务端/客户端都是基于Netty + boringssl的RPC框架,使用TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 仍然是好的,毕竟更安全。

但Https接口,如果不确定对端的是什么,JDK7 SSL or JDK8 SSL or OpenSSL,为免协商出一个超慢的GCM算法,Server端需要通过配置,才决定要不要把GCM放进可选列表里。

2.3 实现

经过一轮学习,平时是这样写的:

SslContext sslContext = SslContextBuilder.forServer(ssc.certificate(), ssc.privateKey())  .sslProvider( SslProvider. OPENSSL).build();

如果想不要开GCM,那把ReferenceCountedOpenSslContext里面的DEFAULT_CIPHERS抄出来,删掉两个GCM的。

List<String> ciphers = Lists.newArrayList(“ECDHE-RSA-AES128-SHA”, “ECDHE-RSA-AES256-SHA”, “AES128-SHA”, “AES256-SHA”, “DES-CBC3-SHA”);

SslContext sslContext = SslContextBuilder.forServer(ssc.certificate(), ssc.privateKey())                               .sslProvider( SslProvider. OPENSSL).ciphers(ciphers).build();

3 结论

  • OpenSSL(boringssl)在我们的测试用例里,比JDK SSL 快10倍,10倍!!! 所以Netty下尽量都要使用OpenSSL。
  • 在确定两端都使用OpenSSL时,TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 仍然是好的,毕竟更安全,也是主流。
  • 对端如果是JDK8 SSL时,Server端要把GCM算法从可选列表里拿掉。
分享到:更多 ()

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址