HTTP 不安全: 明文传输, 任何人都能在链路中截取修改, 伪造请求/响应报文.
安全 的四个特性:
- 机密性: 非可信人不可见
- 完整性: 传输过程无窜改丢失
- 身份认证: 确保消息只能发送给指定身份
- 不可否认: 保证事务的真实性
HTTPS 为 HTTP 添加了以上四个安全特性.
HTTPS 除了协议名与默认端口 443, 协议的具体内容结构完全沿用 HTTP.
HTTPS 中的"S"表示将 HTTP 下层的传输协议由 TCP/IP 换成了 SSL/TLS , 收发报文不再使用 Socket API, 而是调用专门的安全接口.
SSL/TLS 常识
SSL(Secure Sockets Layer)由网景公司发明, 在 v3 版本时被正式标准化并更名为 TLS(Transport Layer Security). TLS1.0 即 SSLv3.1
TLS 已发布了 1.1(2006), 1.2(2008)和 1.3(2018)三个版本, 目前使用最广泛的是 TLS1.2.
TLS 由记录协议、握手协议、警告协议、变更密码规范协议、扩展协议等几个子协议组成, 综合使用了对称加密、非对称加密、身份认证等许多密码学前沿技术。
客户端和服务器各自都支持很多加密算法组合, 这些算法组合称为 密码套件(cipher suite) .
密码套件由特定顺序的算法名称构成, 格式为 密钥交换算法-签名算法-对称加密算法-摘要算法. 如:
密码套件 ECDHE-RSA-AES256-GCM-SHA384 表示 "握手时使用 ECDHE 算法进行密钥交换,用 RSA 签名和身份认证,握手后的通信使用 AES 对称算法,密钥长度 256 位,分组模式是 GCM,摘要算法 SHA384 用于消息认证和 产生随机数".
在客户端和服务器使用 TLS 建立连接时, 会进行协商, 选择一组恰当的密码套件来进行安全通信.
OpenSSL
开源的密码学程序库和工具包, 是 SSL/TLS 的具体实现. 几乎支持所有公开的加密算法和协议, 是事实上的标准.
Nginx, Apache 等常用服务器和软件都使用 OpenSSL 实现 TLS 功能.
机密性: 对称加密与非对称加密
对称加密
指加密和解密使用同一个密钥
TLS 中常用的对称加密算法:
- AES: 应用最广泛的对称加密算法, 密钥长度可以 128,192 或 256 位, 安全性和性能都高
- ChaCha20: Google 设计, 密钥长度固定 256 位, 纯软件性能优于 AES, 但 AES 具有硬件优化, 所以目前相比 AES 不具有明显优势.
分组模式: 使算法可以用固定长度的密钥加密任意长度的明文, 最新的分组模式称为 AEAD , 加密的同时添加了认证功能, 常用的有: GCM CCM Poly1305 .
对称加密算法 + 分组模式构成了 TLS 密码套件中定义的对称加密算法, 如:
AES128-GCM128 位密钥长度的 AES 算法, 分组模式是 GCMChaCha20-Poly1305ChaCha20 算法, 分组模式是 Poly1305
非对称加密
对称加密的问题: 密钥交换过程难以保障机密性.
非对称加密: 具有公钥和私钥, 公钥加密只能私钥解密, 私钥加密只能公钥解密.
- 公钥: 可以公开给任何人, 任何人都可以加密
- 私钥: 必须严格保密, 用于解密
- 私钥加密公钥解密用于后文的 数字签名
非对称加密算法设计比对称算法难得多, TLS 只有少数几种, 常用:
- RSA: 最有名的对称加密算法, 基于"整数分解"数学难题, 使用两个超大素数乘积作为生成密钥的材料, 推荐长度 1024, 目前普遍认为至少 2048 位.
-
ECC: 基于"椭圆曲线离散对数", 用特定的曲线方程和基点生成公私钥, 在安全强度和性能上都明显就优于 RSA, 160 位 ECC 相当于 1024 位 RSA, 密钥短意味着计算量和所需内存带宽就少. 常用曲线:
- P-256: NIST 和 NSA 推荐使用, 密码学界不信任
- x25519: 被认为是最安全最快速的曲线
- secp256k1: 比特币, 以太坊灯区块链技术中使用
混合加密
非对称加密虽然没有"密钥交换"问题, 但运算速度通常比对称加密慢好几个数量级, 经试验 ECC 比 AES 慢了好几百倍.
TLS 中使用的 混合加密 : 通信开始时使用非对称加密进行密钥交换, 之后使用对称加密.
完整性和身份认证
- 完整性问题: 如何保证接收到的消息没有被篡改?
- 身份认证: 如何确保通信双方身份真实可信?
完整性: 摘要算法
摘要算法: 即散列/哈希函数, 将任意长度数据压缩成固定长度且唯一的字符串.
单向算法, 无法解密逆推原文.
常用摘要算法:
- MD5, SHA-1, 安全强度较低, 在 TLS 已禁用
- SHA-2: TLS 推荐使用, 是六种摘要算法的统称, 常用:
SHA224SHA256SHA384
完整性校验: 原文+摘要, 通过原文计算摘要并进行对比.
摘要算法不具备机密性, 建立在机密性之上的完整性: 用会话密钥加密消息和摘要.
身份认证: 数字签名
签名: 使用非对称加密的 私钥 来加密 摘要 , 得到 数字签名.
验签: 数字签名与公钥一样公开, 只有与私钥对应的公钥才能解密得到摘要, 再对比原文验证完整性, 就可以确保通信双方身份和通信内容正确.
一般可以用时间戳和随机数结合做不可逆签名, 避免请求被拦截并重复发送.
公钥的可信性: 数字证书
如何确保得到的公钥就是指定通信方的公钥? 只能通过第三方构建公钥的信任链.
这个第三方就是 CA, 即 Certificate Authority, 证书认证机构.
CA 对各公钥进行签名, 用自己的信誉保证公钥可信. CA 机构有: DigiCert、VeriSign、Entrust、Let’s Encrypt 等.
Let's Encrypt 可颁发免费的 90 天的 DV 证书, 可以用 Certbot 自动续订.
CA 将公钥,序列表,用途,颁发者,有效时间等公钥关联信息打包并签名, 形成 数字证书 .
证书分类:
- DV: 可信度最低, 域名级可信
- OV: 可信度在中间
- EV: 可信度最高, 经过法律和审计的核查可证明网站拥有者身份, 浏览器地址栏会显示公司名称
CA 的信任链: 小的 CA 通过大 CA 进行签名认证, 但最终的 Root CA 只能通过 自签名证书 或 根证书 自己证明自己.
操作系统和浏览器内置了各大 CA 的根证书, 对于服务器发送的证书, 可以顺着证书链层层校验直到找到根证书, 就能够确定证书可信.
证书体系(PKI)弱点: 如果 CA 主动或被动颁发了错误证书, 如何弥补?
- CRL: Certificate revocation list, 证书吊销列表
- OCSP: Online certificate status protocol, 在线证书状态协议
- 终止信任: 直接从操作系统或浏览器中撤销对根 CA 的信任
TLS1.2 的握手过程
基于 ECDHE 密钥交换算法的握手过程
ECDHE: 使用椭圆曲线增强的非对称加密 DH 算法, 公钥私钥都是临时生成的.
-
客户端向服务器发送 "Client Hello" 消息:
Version客户端 TLS 版本号TLS 1.2 (0x0303)Random客户端随机数, 用于后续生成会话秘钥Cipher Suites客户端支持的密码套件列表
// 抓包数据 Handshake Protocol: Client Hello Version: TLS 1.2 (0x0303) Random: 1cbf803321fd2623408dfe... Cipher Suites (17 suites) Cipher Suite: TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 (0xc02f) Cipher Suite: TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (0xc030) -
服务器向客户端返回 "Server Hello" 消息:
Version确认 TLS 版本号Random服务器随机数Cipher Suite从客户端的密码套件列表中选择一项作为本次通信使用的密码套件Server Certificate服务器证书Server Params密钥交换算法参数 + 私钥签名认证- "Server Hello Done"
// 抓包数据 Handshake Protocol: Server Hello Version: TLS 1.2 (0x0303) Random: 0e6320f21bae50842e96... Cipher Suite: TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (0xc030) Handshake Protocol: Server Key Exchange EC Diffie-Hellman Server Params Curve Type: named_curve (0x03) Named Curve: x25519 (0x001d) Pubkey: 3b39deaf00217894e... Signature Algorithm: rsa_pkcs1_sha512 (0x0601) Signature: 37141adac38ea4... -
客户端通过证书链确认证书的真实性, 再用证书公钥验证签名, 得到
Server Params. 同时客户端也生成一个Client Params(密钥交换算法参数)发送给服务器:Handshake Protocol: Client Key Exchange EC Diffie-Hellman Client Params Pubkey: 8c674d0e08dc27b5eaa...此时客户端和服务器都得到了密钥交换算法的两个参数
Client Params和Server Params.客户端和服务端各自利用这两个参数经密钥交换算法(ECDHE)计算得到随机数
Pre-Master.再将得到的三个随机数
Client RandomServer RandomPre-Master作为参数, 通过密码套件中的伪随机函数计算得到加密会话的 48 字节的主秘钥Master Secret.再通过
PRF扩展出client_write_key和server_write_key等会话秘钥. -
客户端再次向服务器发送两个消息:
Change Cipher Spec通知服务器之后改用会话秘钥加密通信Finished之前的所有握手数据进行摘要加密, 发送给服务器进行确认- 使用 ECDHE 实现密钥交换算法时, 此时可以将 HTTP 请求一同发送
-
服务器验证握手数据摘要, 并发送消息进行确认:
Change Cipher Spec之后改用会话秘钥加密通信Finished所有握手数据的摘要- 使用 ECDHE 时可一同发送 HTTP 响应
- 握手正式结束, 之后收发加密的 HTTP 请求和响应.
传统的基于 RSA 的握手过程
与基于 ECDHE 的握手过程类似, 只是 Pre-Master 不需要通过算法生成, 而是由客户端直接生成随机数, 经服务器公钥加密后通过 "Client Key Exchange" 消息发给服务器, 服务器用私钥解密后双方就实现了共享三个随机数, 生成主秘钥.
双向认证
TLS 握手只认证了服务器的身份, 之后可以通过账号, 密码, 验证码等进行客户端身份认证.
对于高安全性场景下, 会使用 U 盾给用户颁发客户端证书, 实现 双向认证 .
双向认证是在 TLS 握手的 "Server Hello Done" 和 "Client Key Exchange" 之间, 由客户端向服务器发送 "Client Certificate" 消息, 服务器收到证书后根据证书链验证客户端身份.
TLS1.3
TLS1.3 的主要改进目标: 兼容、安全、性能.
目前各大浏览器和服务器都支持了 TLS1.3
兼容性
在早期试验中, 一旦将 TLS 头字段中的 Version 改为 TLS1.3(0x304), 会导致大量服务器、网关无法处理, 导致 TLS 握手失败.
为了兼容性, TLS1.3 仍然使用 TLS1.2 的格式, 通过在记录末尾添加一系列扩展字段来增加新功能, 如在握手的 “Hello” 消息后必须有 supported_versions 字段来标记 TLS 版本号:
Handshake Protocol: Client Hello
Version: TLS 1.2 (0x0303)
Extension: supported_versions (len=11)
Supported Version: TLS 1.3 (0x0304)
Supported Version: TLS 1.2 (0x0303)
TLS1.3 的很多功能都是通过扩展字段实现的, 见下文的 性能 部分.
安全
TLS1.3 修补了 TLS1.2 中的很多不安全因素, 升级了一些加密算法, 也废除了很多被证明不安全的算法, 最终只保留了:
- 对称加密算法: AES、ChaCha20
- 分组模式: AEAD 的 GCM、CCM 和 Poly1305
- 摘要算法: SHA256、SHA384
- 密钥交换算法: ECDHE、DHE
- 椭圆曲线: P-256、x25519 等 5 种
加密套件也只有 5 个, 有利于客户端和服务器的选择.
使用 ECDHE 替代 RSA 的原因: RSA 不具备“前项安全”, 一旦拿到了私钥, 就能够破解之前所有的密文. 而 ECDHE 每次握手都生成临时的公私钥, 就算被破解, 也只是一次通信.
性能
将 TLS1.2 的 两个消息往返减少为 1 个(1-RTT), 效率提升了一倍.
TLS1.3 的握手过程中, 客户端在 “Client Hello”中直接在扩展字段 “supportedgroups" 中写明支持的曲线, 并在 “keyshare" 中附带曲线对应的客户端公钥参数, 用 “signaturealgorithms“ 带上签名算法; 服务器收到后选定一个曲线和参数, 再用 “keyshare" 返回服务器的公钥参数, 就实现了密钥交换. 之后的流程与 TLS1.2 相同.
除此之外, TLS1.3 还引入了 “0-RTT” 握手, 满足一些条件时可以在 TCP 连接后立即建立安全连接发送加密消息.
HTTPS 的性能优化
影响 HTTPS 性能的主要因素
分析 TLS 握手过程可知, HTTPS 中较为影响性能的过程有:
- 比 HTTP 增加的 TLS 握手过程, 两个消息往返即 2-RTT;
- 产生(计算)用于密钥交换的临时公私钥对;
- 验证证书过程: 访问 CA 获取 CRL 或 OCSP;
- 非对称加解密(计算) “Pre-Master".
硬件优化
HTTPS 连接是计算密集型而非 I/O 密集型, 所以提升网卡、带宽和存储作用不大.
- 更快的 CPU, 可内建 AES 优化来加速握手和传输;
- SSL 加速卡, 使用专门的硬件做非对称加解密;
- SSL 加速服务器, 专用于 TLS 加解密计算的服务器集群, 性能强大.
软件升级
升级 Linux 内核、Nginx、OpenSSL 版本等;
协议优化
- 尽量采用 TLS1.3, 完全握手只需 1-RTT;
-
若只能用 TLS1.2, 可在服务器中配置:
- 尽量选用椭圆曲线的 ECDHE 算法, 把握手过程减少到 1-RTT;
- 椭圆曲线可选择高性能的 x25519 曲线;
- 对称加密算法可采用“AES128GCM".
证书优化
- 证书传输过程: 可以使用 ECDSA 证书而非 RSA 证书, 节约带宽;
- 证书验证过程: 可以使用 “OCSP Stapling”是服务器预先访问 CA 获取 OCSP 响应并发给客户端, 免去客户端连接 CA 验证证书的过程.
会话复用(Session Ticket)
HTTPS 建立连接时, 先是 TCP 三次握手, 再是 TLS 一次握手, 同时每次连接都要重新计算主密钥, 很影响性能. 可以通过 会话复用 在一次会话中缓存主密钥.
会话复用分为两种:
-
Session ID: 客户端和服务器首次连接后各自保存一个会话 ID, 并存储主密钥和其他信息; 当再次连接时可以用主密钥恢复会话状态, 跳过证书验证和密钥交换, 用一个消息往返建立安全通信.
- 缺点: 对于超大用户量级别的网站来说, 服务器负担过大.
-
Session Ticket: 服务端向客户端发送 “New Session Ticket”, 由客户端保存; 再次连接时, 客户端使用 “session_ticket" 发送 “Ticket”, 有服务器解密验证后可以恢复会话开始加密通信.
- 需要一个固定的密钥文件来加密 Ticket, 为了保证前向安全, 此密钥文件需要定期轮换.
预共享密钥(Pre-shared Key)
原理与 Session Ticket 类似, 发送 Ticket 时会同时带上 Early Data , 从而实现“0-RTT”.
但牺牲了安全性, 权衡方案是只允许安全的 GET/HEAD 方法, 并在消息中加入时间戳、“nonce”验证或“一次性票证”.