Loading... <div class="tip share">请注意,本文编写于 1601 天前,最后修改于 1601 天前,其中某些信息可能已经过时。</div> # 开始使用 ECC 证书 我之前的文章多次提到 ECC 证书,但一直没有专门介绍 ECC 证书的文章,今天补上。本文包含三部分内容:1)简单介绍 ECC 证书是什么;2)介绍如何申请 ECC 证书;3)以 Nginx 为例介绍如何使用 ECC 证书。顺便说下,本站已经用上了 ECC 证书。要查看本站主要支持哪些技术特性,[可以点这里](https://imququ.com/post/readme.html)。<!--more--> ### 简单介绍 HTTPS 通过 TLS 层和证书机制提供了内容加密、身份认证和数据完整性三大功能,可以有效防止数据被监听或篡改,还能抵御 MITM(中间人)攻击。TLS 在实施加密过程中,需要用到非对称密钥交换和对称内容加密两大算法。 对称内容加密强度非常高,加解密速度也很快,只是无法安全地生成和保管密钥。在 TLS 协议中,应用数据都是经过对称加密后传输的,传输中所使用的对称密钥,则是在握手阶段通过非对称密钥交换而来。常见的 AES-GCM、ChaCha20-Poly1305,都是对称加密算法。 非对称密钥交换能在不安全的数据通道中,产生只有通信双方才知道的对称加密密钥。目前最常用的密钥交换算法有 RSA 和 ECDHE:RSA 历史悠久,支持度好,但不支持 PFS(Perfect Forward Secrecy);而 ECDHE 是使用了 ECC(椭圆曲线)的 DH(Diffie-Hellman)算法,计算速度快,支持 PFS。要了解更多 RSA 和 ECDHE 密钥交换的细节,可以阅读 Cloudflare 的[这篇文章](https://blog.cloudflare.com/keyless-ssl-the-nitty-gritty-technical-details/)。 只有非对称密钥交换,依然无法抵御 MITM 攻击,还得引入身份认证机制。对于大部分 HTTPS 网站来说,服务端一般通过 HTTP 应用层的帐号体系就能完成客户端身份认证;而浏览器想要验证服务端身份,需要用到服务端提供的证书。 浏览器会在两个步骤中用到证书:1)证书合法性校验。确保证书由合法 CA 签署,且适用于当前网站;2)使用证书提供的非对称加密公钥,完成密钥交换和服务端认证。 证书合法性校验的原理,简单总结如下: * 根据版本号、序列号、签名算法标识、发行者名称、有效期、证书主体名、证书主体公钥信息、发行商唯一标识、主体唯一标识、扩展等信息,生成 TBSCertificate(To Be Signed Certificate)信息; * 签发数字签名:使用 HASH 函数对 TBSCertificate 计算得到消息摘要,再用 CA 的私钥进行加密,得到签名; * 校验数字签名:使用相同的 HASH 函数对 TBSCertificate 计算得到消息摘要,与使用 CA 公钥解密签名得到内容相比较; 可以看到校验证书需要同时用到签名和非对称加密算法:目前必须使用 SHA-2 做为证书签名函数(没有打 XP SP3 补丁的 IE6 不支持);目前一般使用 RSA 算法对 TBSCertificate 进行非对称加密。可以通过 openssl 工具来查看证书签名算法: ```shell $ openssl x509 -in chained.pem -noout -text | grep 'Signature Algorithm' Signature Algorithm: sha256WithRSAEncryption ``` 大部分 CA 都有证书链,浏览器对于收到的多级证书,需要从站点证书开始逐级验证,直至出现操作系统或浏览器内置的受信任 CA 根证书。 浏览器还需要校验当前访问的域名是否存在于证书 TBSCertificate 的 `Common Name` 或 `Subject Alternative Name` 字段之中。 在 RSA 密钥交换中,浏览器使用证书提供的 RSA 公钥加密相关信息,如果服务端能解密,意味着服务端拥有证书对应的私钥,同时也能算出对称加密所需密钥。密钥交换和服务端认证合并在一起。 在 ECDHE 密钥交换中,服务端使用证书私钥对相关信息进行签名,如果浏览器能用证书公钥验证签名,就说明服务端确实拥有对应私钥,从而完成了服务端认证。密钥交换和服务端认证是完全分开的。 可用于 ECDHE 数字签名的算法主要有 RSA 和 ECDSA,也就是目前密钥交换 + 签名有三种主流选择: * RSA 密钥交换(无需签名); * ECDHE 密钥交换、RSA 签名; * ECDHE 密钥交换、ECDSA 签名; 以下是 Chrome 中这三种密钥交换方式的截图(截图来自于早期 Chrome,新版 Chrome 查看位置有了变化): <img alt="key exchange" src="https://st.imququ.com/static/uploads/2016/03/key_exchange.png" itemprop="image" width="790" /> 内置 ECDSA 公钥的证书一般被称之为 ECC 证书,内置 RSA 公钥的证书就是 RSA 证书。由于 256 位 ECC Key 在安全性上等同于 3072 位 RSA Key,加上 ECC 运算速度更快,ECDHE 密钥交换 + ECDSA 数字签名无疑是最好的选择。由于同等安全条件下,ECC 算法所需的 Key 更短,所以 ECC 证书文件体积比 RSA 证书要小一些。以下是本站的对比,可以看到左侧的 ECC 证书要小 1/3: <img src="https://st.imququ.com/static/uploads/2016/08/ecc-certificate-vs-rsa-certificate.png" width="550" alt="ecc certificate vs rsa certificate" itemprop="image" style=""> RSA 证书可以用于 RSA 密钥交换(RSA 非对称加密)或 ECDHE 密钥交换(RSA 非对称签名);而 ECC 证书只能用于 ECDHE 密钥交换(ECDSA 非对称签名)。 并不是所有浏览器都支持 ECDHE 密钥交换,也就是说 ECC 证书的兼容性要差一些。例如在 Windows XP 中,使用 ECC 证书的网站只有 Firefox 能访问(Firefox 的 TLS 自己实现,不依赖操作系统);Android 平台中,也需要 Android 4+ 才支持 ECC 证书。 好消息是,Nginx 1.11.0 开始提供了对 RSA/ECC 双证书的支持。它的实现原理是:分析在 TLS 握手中双方协商得到的 Cipher Suite,如果支持 ECDSA 就返回 ECC 证书,否则返回 RSA 证书。 也就是说,配合最新的 Nginx,我们可以使用 ECC 证书为现代浏览器提供更好的体验,同时老旧浏览器依然会得到 RSA 证书,从而保证了兼容性。这一次,鱼与熊掌可以兼得。 ### 如何申请 如果你的 CA 支持签发 ECC 证书,使用以下命令生成 CSR(Certificate Signing Request,证书签名请求)文件并提交给提供商,就可以获得 ECC 证书: ```shell openssl ecparam -genkey -name secp256r1 | openssl ec -out ecc.key openssl req -new -key ecc.key -out ecc.csr ``` 以上命令中可供选择的算法有 secp256r1 和 secp384r1,secp521r1 已被 Chrome 和 Firefox 废弃。 我目前在用的 Let's Encrypt,也支持签发 ECC 证书。我使用了 [acme.sh](https://github.com/Neilpang/acme.sh) 这个小巧的工具来签发证书,指定 `-k ec-256` 就可以将证书类型改为 ECC: ```shell "/root/.acme.sh"/acme.sh --issue --dns dns_cx -d imququ.com -d www.imququ.com -k ec-256 ``` 目前 Let's Encrypt 只提供 RSA 中间证书,官方预计会在 2017 年 3 月底提供 ECC 中间证书([via](https://letsencrypt.org/upcoming-features/))。 ### 如何使用 有了 RSA/ECC 双证书之后,还需要安装 Nginx 1.11.x。这部分内容我之前详细写过,[请点击查看](https://imququ.com/post/my-nginx-conf.html)。 一切准备妥当后,将证书配置改为双份即可: ```nginx ssl_certificate example.com.rsa.crt; ssl_certificate_key example.com.rsa.key; ssl_certificate example.com.ecdsa.crt; ssl_certificate_key example.com.ecdsa.key; ``` 问题来了!本站使用 Cloudflare 提供的 Cipher Suites 配置,在 Nginx 中配置了双证书并重启,用 Chrome 测试发现仍然没有采用 ECC 证书。这是为什么呢? ```nginx # https://github.com/cloudflare/sslconfig/blob/master/conf ssl_ciphers EECDH+CHACHA20:EECDH+CHACHA20-draft:EECDH+AES128:RSA+AES128:EECDH+AES256:RSA+AES256:EECDH+3DES:RSA+3DES:!MD5; ssl_prefer_server_ciphers on; ``` 研究发现,Chrome 与服务端协商到的 Cipher Suites 是 `ECDHE-RSA-AES128-GCM-SHA256`,来自于 `ssl_ciphers` 配置中的 `EECDH+AES128` 这部分。我们通过 openssl 工具看一下 `EECDH+AES128` 具体包含哪些 Cipher Suites: ```shell openssl ciphers -V 'EECDH+AES128' | column -t 0xC0,0x2F - ECDHE-RSA-AES128-GCM-SHA256 TLSv1.2 Kx=ECDH Au=RSA Enc=AESGCM(128) Mac=AEAD 0xC0,0x2B - ECDHE-ECDSA-AES128-GCM-SHA256 TLSv1.2 Kx=ECDH Au=ECDSA Enc=AESGCM(128) Mac=AEAD 0xC0,0x27 - ECDHE-RSA-AES128-SHA256 TLSv1.2 Kx=ECDH Au=RSA Enc=AES(128) Mac=SHA256 0xC0,0x23 - ECDHE-ECDSA-AES128-SHA256 TLSv1.2 Kx=ECDH Au=ECDSA Enc=AES(128) Mac=SHA256 0xC0,0x13 - ECDHE-RSA-AES128-SHA SSLv3 Kx=ECDH Au=RSA Enc=AES(128) Mac=SHA1 0xC0,0x09 - ECDHE-ECDSA-AES128-SHA SSLv3 Kx=ECDH Au=ECDSA Enc=AES(128) Mac=SHA1 ``` 可以看到,使用 RSA 做为签名认证算法(Au=RSA)的加密套件排到了前面,导致 Nginx 作出了错误判断。 知道原因就好办了,将这段配置改为 `EECDH+ECDSA+AES128:EECDH+aRSA+AES128`,再看一下: ```shell openssl ciphers -V 'EECDH+ECDSA+AES128:EECDH+aRSA+AES128' | column -t 0xC0,0x2B - ECDHE-ECDSA-AES128-GCM-SHA256 TLSv1.2 Kx=ECDH Au=ECDSA Enc=AESGCM(128) Mac=AEAD 0xC0,0x23 - ECDHE-ECDSA-AES128-SHA256 TLSv1.2 Kx=ECDH Au=ECDSA Enc=AES(128) Mac=SHA256 0xC0,0x09 - ECDHE-ECDSA-AES128-SHA SSLv3 Kx=ECDH Au=ECDSA Enc=AES(128) Mac=SHA1 0xC0,0x2F - ECDHE-RSA-AES128-GCM-SHA256 TLSv1.2 Kx=ECDH Au=RSA Enc=AESGCM(128) Mac=AEAD 0xC0,0x27 - ECDHE-RSA-AES128-SHA256 TLSv1.2 Kx=ECDH Au=RSA Enc=AES(128) Mac=SHA256 0xC0,0x13 - ECDHE-RSA-AES128-SHA SSLv3 Kx=ECDH Au=RSA Enc=AES(128) Mac=SHA1 ``` 这下就没问题了。 并不是所有加密套件都需要把 ECDSA 和 aRSA 分开写,例如 `EECDH+CHACHA20` 就不需要,ECDSA 默认就在前面: ```shell openssl ciphers -V 'EECDH+CHACHA20' | column -t 0xCC,0xA9 - ECDHE-ECDSA-CHACHA20-POLY1305 TLSv1.2 Kx=ECDH Au=ECDSA Enc=ChaCha20-Poly1305 Mac=AEAD 0xCC,0xA8 - ECDHE-RSA-CHACHA20-POLY1305 TLSv1.2 Kx=ECDH Au=RSA Enc=ChaCha20-Poly1305 Mac=AEAD ``` 最终,我的 Cipher Suites 配置如下,供参考: ```nginx ssl_ciphers EECDH+CHACHA20:EECDH+CHACHA20-draft:EECDH+ECDSA+AES128:EECDH+aRSA+AES128:RSA+AES128:EECDH+ECDSA+AES256:EECDH+aRSA+AES256:RSA+AES256:EECDH+ECDSA+3DES:EECDH+aRSA+3DES:RSA+3DES:!MD5; ``` 原文链接:[https://imququ.com/post/ecc-certificate.html](https://imququ.com/post/ecc-certificate.html), 最后修改:2020 年 08 月 04 日 © 允许规范转载 打赏 赞赏作者 支付宝微信 赞 如果觉得我的文章对你有用,请随意赞赏