记录最近对 TLS 的一点探索
定义下我当时所用的版本,都是最新的稳定版
nginx_version=”1.18.0”
openssl_version=”1.1.1h”
很喜欢这些东西,想记录一下,废话不多说,直接开始
编译 Nginx
这里重新审视了我的行为,我认为是不妥的,编译安装对新手来说不是一个好选择,通过包管理器来安装软件在 Linux 永远都是最推荐的。具体原因在下一篇文章讲
依赖部分不多讲述,使用 GNU/Linux 上通用的 build-essential 就可以了
1 | //在 /usr/local/src 目录下载源码包 |
之后正式开始编译
1 | cd nginx-1.18.0 |
之后参考官方文档 Building nginx from Sources 编写对应目录的 systemd 启动服务脚本
systemctl start nginx 就可以运行了
配置 Nginx
我最初的目标是配置出一个仅支持 TLS 1.3 的网站,而在这个过程中踩了一些坑,虽然解决了,但仍然不能明确知道为何解决了,所以记录一下
我最开始的配置是
1 | server { |
甚至不能通过 nginx -t 的配置检查,提示 no cipher match,搜索了一下,也有人遇到同样的问题并向官方汇报了,得到的回复是 OpenSSL 不喜欢这些字符串?或许我不应该直接指定 cipher suites(密码套件)?
而且 TLS13-AES-256-GCM-SHA384:TLS13-CHACHA20-POLY1305-SHA256:TLS13-AES-128-GCM-SHA256;
这个写法是直接借鉴了一些文章,但我四处搜索也没有找到这个写法的出处,可能是 2018 当年 TLS 1.3 刚刚发表时留下的一些历史遗留?故我将 cipher suites 改为我从 OpenSSL 查到的标准写法 TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256;
,仍然不能通过 nginx -t 的配置检查。无奈,我只能将支持的协议版本改为
1 | ssl_protocols TLSv1.2 TLSv1.3; |
并添加了一个 TLS 1.2 的 cipher suites,这回总算能通过配置检查了,Nginx 成功启动,在 SSL Labs 得到的结果是站点仅支持我指定的那种 TLS 1.2 的 cipher suites,看来还是有点效果,但未完全解决问题,于是寻找原因
1.编译参数。最开始我的编译参数中并没有加上 --with-openssl-opt=enable-tls1_3
,因为在一些文章中提到这个参数是默认开启的,故我加上了这个参数并重新编译(后来想想确实不是这个问题)
2.继续搜索相关问题,找到了 Let’s Encrypt 社区的一个回答 Let’s Encrypt Community - TLSv1.3 not working on Fedora 31 and Nginx 1.16.1,这里面解释说,如果你的 default_server 没有开启 ssl_protocols TLSv1.3
,所有配置都会 Default 到 TLSv1.3 之下,因为我确实还多监听了 default_server(防止扫描证书
1 | server { |
怎么 Default
有人说是 ssl_ciphers 对 TLS 1.3 无效,其实不准确,ssl_ciphers 是可以控制 TLS 1.3 的密码套件选择顺序的。经过我的测试,来讲讲如果不为 default_server 开启 ssl_protocols TLSv1.3
会怎么样
1 | ssl_protocols TLSv1.2 TLSv1.3; |
网站仅会支持 ECDHE-ECDSA-AES128-GCM-SHA256 这一种 cipher suites
1 | ssl_protocols TLSv1.2 TLSv1.3; |
网站会支持所有的 TLSv1.2 cipher suites,包括有安全隐患的 CBC 模式密码和非 AEAD 算法
1 | ssl_protocols TLSv1.3; |
这种写法,网站会 Default 到支持所有的 TLSv1.0、TLSv1.1、TLSv1.2 cipher suites,如果你这时去 SSL Labs 测试,会看到一大片被标记为黄色的 WEAK 警告
测试开启仅 TLS 1.3
虽然仍不能百分百确定原因,但我暂时也不想再去编译 Nginx 了,因为我已经可以实现最初的想法了,配置出一个仅支持 TLS 1.3 的网站,根本不需要指定什么 cipher suites,简单的在你所有 server {} 块里加上 ssl_protocols TLSv1.3;
即可。遵循 Mozilla Wiki - Security/Server Side TLS 的建议,最终,我的配置如下
开启仅 TLS 1.3 在如今仍是非常激进的做法,这只是我的一个实验。出于对旧设备和软件的兼容,不推荐读者这样配置密码套件
1 | server { |
虽说问题与 cipher suites 无关,但误打误撞,我对密码学稍微有了一些了解
一点简介
在 Linux 系统中安装 openssl 之后,使用 openssl ciphers -v -stdname | column -t
查看密码套件列表,一共有 60 行
去除掉非 AEAD 算法、TLS 1.2 以下的 ciphers、不具备前向安全性的 RSA 密钥交换算法后,陈列如下
1 | TLS_AES_256_GCM_SHA384 - TLS_AES_256_GCM_SHA384 TLSv1.3 Kx=any Au=any Enc=AESGCM(256) Mac=AEAD |
看起来也不是很多对吧,其中第一列就是可以在 Nginx 中指定的 cipher suites,当然也可以指定类,比如 ECDH+ECDSA,会包含这类密码套件
呃,本来想写点东西,可这块着实有些艰深,我肯定讲的会没有深度甚至有错误,所以只简单谈点经验了
名词介绍
1 | ECC Elliptic-curve cryptography 椭圆曲线密码学 |
经验
不要把 RSA 用于密钥交换,没有前向安全性。应该使用 DHE/ECDHE,E 表示 Ephemeral,临时 DH/ECDH,能够提供前向安全性,同时 TLS 1.3 也只支持 (EC)DHE 密钥协商算法,并且椭圆曲线优先,即 ECDHE 优先
针对证书公钥的选择,有 RSA 和 ECC 可选。根据 GnuPG 的建议,如果选择 RSA 算法做非对称加密,2048 位就已足够,如果你非要使用更高位的 RSA,那你应该去使用 ECC,256 位 ECC 便可提供相当于 3072 位 RSA 的密码强度。因为 RSA 算法随着位数的增长,资源消耗是指数上升的。无论在安全等级还是计算速度,ECC 都占绝对的优势
“Because it gives us almost nothing, while costing us quite a lot/收获很少,却付出了很多”
仅出于个人,我更喜欢 ECDHE_ECDSA,同样是因为密钥更小,传输更快。ECDSA 仅需要 256 位大小的公钥即可提供相当于 RSA 3072 位的安全级别,如果使用 ECC 证书,或许应该首选 ECDHE_ECDSA
在曲线的选择上应尽量避开 NIST 的几条曲线,以 X25519 优先,X25519 在设计上是完全透明的并且安全性更好
参考
Halfrost-Field 冰霜之地 - Protocol
Tech Explorer - TLS协议分析 与 现代加密通信协议设计
Jerry Qu - 开始使用 ECC 证书
Shell’s Home - 安全协议的设计