Netty SSL性能调优

TLS算法组合

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

  • 认证算法linux

  • 加密算法算法

  • 消息认证码算法 简称MAC安全

  • 密钥交换算法服务器

  • 密钥衍生算法session

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

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

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

SSL协议的握手过程

第一步,爱丽丝给出协议版本号、一个客户端生成的随机数(Client random),以及客户端支持的加密方法。性能

第二步,鲍勃确认双方使用的加密方法,并给出数字证书、以及一个服务器生成的随机数(Server random)。

第三步,爱丽丝确认数字证书(对证书信息进行md5或者hash后的编号==用证书机构的公钥对加密的证书编号解密后的证书编号)有效,而后生成一个新的随机数(Premaster secret),并使用数字证书中的公钥(鲍勃的公钥),加密这个随机数,发给鲍勃。

第四步,鲍勃使用本身的私钥,获取爱丽丝发来的随机数(即Premaster secret)。

第五步,爱丽丝和鲍勃根据约定的加密方法,使用前面的三个随机数,生成"对话密钥"(session key),用来加密接下来的整个对话过程。

https要使客户端与服务器端的通讯过程获得安全保证,必须使用对称加密算法而且每一个客户端的算法都不同,须要一个协商过程,可是协商对称加密算法的过程,须要使用非对称加密算法来保证安全,直接使用非对称加密的过程自己也不安全,会有中间人篡改公钥的可能性,因此客户端与服务器不直接使用公钥,而是使用数字证书签发机构颁发的证书来保证非对称加密过程自己的安全。这样经过这些机制协商出一个对称加密算法,就此双方使用该算法进行加密解密。从而解决了客户端与服务器端之间的通讯安全问题。

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,还差着一个量级。

Netty 对SSL的支持

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

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

性能问题的出现

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

缘由分析

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放进可选列表里。

解决方法

平时是这样写的:

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();

总结

  • OpenSSL(boringssl)比JDK SSL 快10倍,10倍!!! 因此Netty下尽可能都要使用OpenSSL。

  • 在肯定两端都使用OpenSSL时,TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 仍然是好的,毕竟更安全,也是主流。

  • 对端若是是JDK8 SSL时,Server端要把GCM算法从可选列表里拿掉。

相关文章
相关标签/搜索