作者原始博客地址为 https://blog.ertuil.top/post/tech/security-tls/。
如今已经是 2019 年了,大部分的 HTTP 服务器都默认配置了 SSL/TLS ,以为大家提供安全的、具有隐私性的网络服务。
但是安全的配置 TLS 往往是困难的,如果配置不当可能会导致性能低下、安全性能减弱等问题。因此这篇文章以 Nginx 服务器为例,讲解如何安全的配置 ssl 服务器。
早期的 SSL(安全套接字协议)是由网景公司发布的提供安全连接的服务,发布了 SSL1.0, SSL2.0, SSL 3.0 三个版本。后来 IETF 将其标准化,并作为 TLS 协议(传输层安全协议)发布了 TLSv1.1 和 TLSv1.2 协议。 2018年,最新的 TLSv1.3 协议发布。目前主要使用的版本是 TLSv1.2 和 TLSv1.3 两个版本,早期的版本已经不再建议使用了。
TLS 协议主要使用了如下一些密码学算法来提供安全服务:
为了完成上面这些密码学服务,需要使用一些密码学算法,来提供这些服务。将这些密码学算法组合起来,形成一个个套餐,也就形成了密码学套件(ciphers)。
TLS 协议最初是的阶段,就是通信双方对于所使用的的密码学套件进行协商,确定算法和相关参数,之后才能提供安全性服务。
为了验证身份,TLS 使用了一种被称为“数字签名”的算法。数字签名是一种基于非对称密码的算法,可以使得一个实体(一般来说是服务器)证明他就是所声称的人。
这里就需要大家常说的证书的概念,简单来说,证书提供了关系的证明,https 服务器需要向可信第三方(CA)申请一个 ssl 证书。客户端拿到服务器的证书之后,去CA处查询,就能够觉得服务器的身份。
数字签名根据所使用的非对称算法的不同,主要有有 RSA 和 ECDSA 两种证书。
RSA(DSA) 是最简单的一种非对称算法,可以用于签名,但是其秘钥比较长(通常有 2048 位或者 4096 位),计算的开销比较大。因此近些年搭建广泛使用 ECDSA 来取代 RSA 算法。
ECDSA 是在椭圆曲线上的签名算法,秘钥较短,计算开销也非常小。ECDSA 的缺点就是由于才推行,因此兼容性交叉,一般需要同时提供 RSA 作为备选方案。
RSA 还是 ECDSA 的选取是根据你所申请的证书的类型来决定的。
秘钥交换是指服务器和客户端协商出一个秘钥,用于后续加密。
这里常用的密码学算法有:
后续出现了增强的 DH 和 ECDH 协议,被称为 DHE 和 ECDHE 协议。这两个协议具有较高的安全性能。
TLSv1.2 之前的协议中支持 RSA,DHE 和 ECDHE 这些算法。但是 RSA 已经不被推荐使用了。同时又由于 ECDHE 可以实现 DHE 类似的安全性能,但是有有较低的计算开销,因此在 TLSv1.3 之后,默认使用 ECHDE。
但是在 TLSv1.2中也建议使用 ECDHE 算法。
加密算法用于对消息进行加密,常见的加密算法有如下这些:
消息认证算法有消息验证码(MAC)、HMAC等。TLS协议使用的是 HMAC 算法,根据其哈希函数的不同,常见的有 SHA256, SHA384,SHA1,MD5 等算法。其中 MD5 和 SHA1 应当已经不再使用了。
后来,人们发现将加密算法和消息认证算法分开使用,会有各种安全问题,因此发明了一类同时进行加密和认证的算法,被称为 AEAD 算法。这一类算法是目前非常推荐使用的。
AEAD 算法将加密和认证同时进行,简化了操作步骤,又有非常高的安全性,因此在 TLSv1.3 中只使用 AEAD 算法,而不再单独指定加密和认证算法。
常见的 AEAD 算法有:AES-GCM, AES-CCM, CHACHA20 等。
将上面所说的算法组合起来,形成的套餐,就是密码套件,TLS 中为套件分配了编号。在建立 TLS 连接的过程中,服务器和客户端会协商选取出一个套件来使用。
比如:
选取密码学套件的矛盾在于,安全性很强的密码学算法只有现代的浏览器和服务器支持;如果想兼容较为古老的客户端,可能需要使用安全性较弱的密码学套件。当然根据优先级的配置,可以使得新的客户端使用较强的密码学算法,而老旧客户端使用相对较弱的算法。
在 TLSv1.2 之后,引入了会话重用的概念,可是使得服务器和客户端第二次建立连接之后,可以使用上次会话的参数,简化了握手流程,加快了连接建立的速度。
传统 TLS 协议在握手完成之前,是不能发送消息内容的。在 TLSv1.3 之后,引入了 0RTT 的机制,可以使得在建立连接的过程之中,就发送了上层数据。极大的提高了用户的使用感受。
但是这种机制是有代价的,0RTT 发送的信息安全性较弱,可能带来重放攻击!
下面以 Nginx 配置为例,讲解目前(2019年)配置推荐。
目前SSL1.0, SSL2.0, SSL3.0, TLSv1.1 版本都不再使用。目前最近的 TLSv1.3 应当首选使用,但是由于其支持还不足(Windows 8,Java 8等),因此为了兼容性,可以使用 TLSv1.2 版本。
使用下面的指令来指定证书的公私钥文件。可以多次使用下面两个指令,分别指定 RSA 和 ECDSA 的证书。
ssl_certificate fullchain.pub; # 公钥文件
ssl_certificate_key daemon.key; 秘钥文件
下面两种指令决定了会话缓存的大小和保留的时间。
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 1d;
目前 TLSv1.3 的所有密码学套件都推荐使用。
TLSv1.2 中有一些密码学套件不再推荐使用。
因此如下推荐:
ssl_ciphers TLS13-AES-256-GCM-SHA384:TLS13-CHACHA20-POLY1305-SHA256:TLS13-AES-128-GCM-SHA256:EECDH+CHACHA20:EECDH+CHACHA20-draft:EECDH+aRSA+AESGCM:EECHD+ECDSA+AESGCM:EECDH+ECDSA+AESCCM:EECHD+aRSA+AESCCM:EECDH+aRSA+AES:!aNULL:!MD5:!RC4:!EXP:!LOW;
关于 ciphers lists 的语法简要说明如下:
openssl 支持的密码套件可以使用命令 openssl ciphers -V |column -t
来查看。
如果不使用 ECDSA 的证书,则可以将 EECHD+ECDSA+AESGCM 和 EECDH+ECDSA+AESCCM 去除。
如果不需要指出 AES-CBC 则将 EECDH+aRSA+AES 去除。
由于其有重放攻击可能性,因此 Nginx 默认不开启,如需开启:
ssl_early_data on;
其他 ssl 指令参照 Nginx 官网配置,如 ssl_stapling 指令表示是否开启 OCSP stapling,以验证其状态等。
HSTS HTTP 严格传输安全协议。添加这个头标,要求以后浏览器自动请求 https 协议。
add_header Strict-Transport-Security "max-age=31536000";
用于将 http 重定向到 HTTPS 。
server {
listen 80;
listen [::]:80;
server_name xxx.com;
rewrite ^(.*)$ https://$host$1 permanent;
}