硬件优化
因为 HTTPS 是属于计算密集型,应该选择计算力更强的 CPU,而且最好选择支持 AES-NI 特性的 CPU,这个特性可以在硬件级别优化 AES 对称加密算法,加快应用数据的加解密。
软件优化
如果可以,把软件升级成较新的版本,比如将 Linux 内核 2.X 升级成 4.X,将 openssl 1.0.1 升级到 1.1.1,因为新版本的软件不仅会提供新的特性,而且还会修复老版本的问题。
协议优化
- 密钥交换算法应该选择 ECDHE 算法,而不用 RSA 算法,因为 ECDHE 算法具备前向安全性,而且客户端可以在第三次握手之后,就发送加密应用数据,节省了 1 RTT。
- 将 TLS1.2 升级 TLS1.3,因为 TLS1.3 的握手过程只需要 1 RTT,而且安全性更强。
证书优化
证书传输方向:
- 服务器应该选用 ECDSA 证书,而非 RSA 证书,因为在相同安全级别下,ECC 的密钥长度比 RSA 短很多,这样可以提高证书传输的效率;
证书认证方向:
为了知道证书是否被 CA 吊销,客户端有时还会再去访问 CA, 下载 CRL 或者 OCSP 数据,以此确认证书的有效性。这个访问过程是 HTTP 访问,因此又会产生一系列网络通信的开销,如 DNS 查询、建立连接、收发数据等。
CRL
CRL 称为证书吊销列表(Certificate Revocation List),这个列表是由 CA 定期更新,列表内容都是被撤销信任的证书序号,如果服务器的证书在此列表,就认为证书已经失效,不在的话,则认为证书是有效的。
但是 CRL 存在两个问题:
- 第一个问题,由于 CRL 列表是由 CA 维护的,定期更新,如果一个证书刚被吊销后,客户端在更新 CRL 之前还是会信任这个证书,实时性较差;
- 第二个问题,随着吊销证书的增多,列表会越来越大,下载的速度就会越慢,下载完客户端还得遍历这么大的列表,那么就会导致客户端在校验证书这一环节的延时很大,进而拖慢了 HTTPS 连接。
OCSP
因此,现在基本都是使用 OCSP ,名为在线证书状态协议(Online Certificate Status Protocol)来查询证书的有效性,它的工作方式是向 CA 发送查询请求,让 CA 返回证书的有效状态。
不必像 CRL 方式客户端需要下载大大的列表,还要从列表查询,同时因为可以实时查询每一张证书的有效性,解决了 CRL 的实时性问题。
OCSP 需要向 CA 查询,因此也是要发生网络请求,而且还得看 CA 服务器的“脸色”,如果网络状态不好,或者 CA 服务器繁忙,也会导致客户端在校验证书这一环节的延时变大。
OCSP Stapling
于是为了解决这一个网络开销,就出现了 OCSP Stapling,其原理是:服务器向 CA 周期性地查询证书状态,获得一个带有时间戳和签名的响应结果并缓存它。
当有客户端发起连接请求时,服务器会把这个「响应结果」在 TLS 握手过程中发给客户端。由于有签名的存在,服务器无法篡改,因此客户端就能得知证书是否已被吊销了,这样客户端就不需要再去查询。
服务器应该开启 OCSP Stapling 功能,由服务器预先获得 OCSP 的响应,并把响应结果缓存起来,这样 TLS 握手的时候就不用再访问 CA 服务器,减少了网络通信的开销,提高了证书验证的效率;
重连优化
对于重连 HTTPS 时,可以使用上一次 HTTPS 连接使用的会话密钥,直接恢复会话,而不用再重新走完整的 TLS 握手过程,减少 TLS 握手的性能损耗。
常见的会话重用技术有 Session ID 和 Session Ticket,用了会话重用技术,当再次重连 HTTPS 时,只需要 1 RTT 就可以恢复会话。对于 TLS1.3 使用 Pre-shared Key 会话重用技术,只需要 0 RTT 就可以恢复会话。
Session ID
Session ID 的工作原理是,客户端和服务器首次 TLS 握手连接后,双方会在内存缓存会话密钥,并用唯一的 Session ID 来标识,Session ID 和会话密钥相当于 key-value 的关系。
当客户端再次连接时,hello 消息里会带上 Session ID,服务器收到后就会从内存找,如果找到就直接用该会话密钥恢复会话状态,跳过其余的过程,只用一个消息往返就可以建立安全通信。当然为了安全性,内存中的会话密钥会定期失效。
但是它有两个缺点:
- 服务器必须保持每一个客户端的会话密钥,随着客户端的增多,服务器的内存压力也会越大。
- 现在网站服务一般是由多台服务器通过负载均衡提供服务的,客户端再次连接不一定会命中上次访问过的服务器,于是还要走完整的 TLS 握手过程;
Session Ticket
为了解决 Session ID 的问题,就出现了 Session Ticket,服务器不再缓存每个客户端的会话密钥,而是把缓存的工作交给了客户端,类似于 HTTP 的 Cookie。
客户端与服务器首次建立连接时,服务器会加密「会话密钥」作为 Ticket 发给客户端,交给客户端缓存该 Ticket。
客户端再次连接服务器时,客户端会发送 Ticket,服务器解密后就可以获取上一次的会话密钥,然后验证有效期,如果没问题,就可以恢复会话了,开始加密通信。
对于集群服务器的话,要确保每台服务器加密 「会话密钥」的密钥是一致的,这样客户端携带 Ticket 访问任意一台服务器时,都能恢复会话。
Session ID 和 Session Ticket 都不具备前向安全性,因为一旦加密「会话密钥」的密钥被破解或者服务器泄漏「会话密钥」,前面劫持的通信密文都会被破解。
同时应对重放攻击也很困难,这里介绍重放攻击工作的原理。
假设 Alice 想向 Bob 证明自己的身份。 Bob 要求 Alice 的密码作为身份证明,爱丽丝应尽全力提供(可能是在经过如哈希函数的转换之后)。与此同时,Eve 窃听了对话并保留了密码(或哈希)。
交换结束后,Eve(冒充 Alice )连接到 Bob。当被要求提供身份证明时,Eve 发送从 Bob 接受的最后一个会话中读取的 Alice 的密码(或哈希),从而授予 Eve 访问权限。
重放攻击的危险之处在于,如果中间人截获了某个客户端的 Session ID 或 Session Ticket 以及 POST 报文,而一般 POST 请求会改变数据库的数据,中间人就可以利用此截获的报文,不断向服务器发送该报文,这样就会导致数据库的数据被中间人改变了,而客户是不知情的。
避免重放攻击的方式就是需要对会话密钥设定一个合理的过期时间。
前面的 Session ID 和 Session Ticket 方式都需要在 1 RTT 才能恢复会话。
而对于重连 TLS1.3 只需要 0 RTT,原理和 Ticket 类似,只不过在重连时,客户端会把 Ticket 和 HTTP 请求一同发送给服务端,这种方式叫 Pre-shared Key。
同样的,Pre-shared Key 也有重放攻击的危险。
这些会话重用技术虽然好用,但是存在一定的安全风险,它们不具备前向安全,而且有重放攻击的风险,所以应当对会话密钥设定一个合理的过期时间,以及只针对安全的 HTTP 请求如 GET/HEAD 使用会话重用。