HTTP之三:安全的HTTPS

August 25, 2021

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-GCM 128 位密钥长度的 AES 算法, 分组模式是 GCM
  • ChaCha20-Poly1305 ChaCha20 算法, 分组模式是 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 推荐使用, 是六种摘要算法的统称, 常用: SHA224 SHA256 SHA384

完整性校验: 原文+摘要, 通过原文计算摘要并进行对比.

摘要算法不具备机密性, 建立在机密性之上的完整性: 用会话密钥加密消息和摘要.

身份认证: 数字签名

签名: 使用非对称加密的 私钥 来加密 摘要 , 得到 数字签名.

验签: 数字签名与公钥一样公开, 只有与私钥对应的公钥才能解密得到摘要, 再对比原文验证完整性, 就可以确保通信双方身份和通信内容正确.

一般可以用时间戳和随机数结合做不可逆签名, 避免请求被拦截并重复发送.

公钥的可信性: 数字证书

如何确保得到的公钥就是指定通信方的公钥? 只能通过第三方构建公钥的信任链.

这个第三方就是 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 算法, 公钥私钥都是临时生成的.

  1. 客户端向服务器发送 "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)
    
  2. 服务器向客户端返回 "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...
    
  3. 客户端通过证书链确认证书的真实性, 再用证书公钥验证签名, 得到 Server Params. 同时客户端也生成一个 Client Params (密钥交换算法参数)发送给服务器:

    Handshake Protocol: Client Key Exchange
        EC Diffie-Hellman Client Params
            Pubkey: 8c674d0e08dc27b5eaa...
    

    此时客户端和服务器都得到了密钥交换算法的两个参数 Client ParamsServer Params .

    客户端和服务端各自利用这两个参数经密钥交换算法(ECDHE)计算得到随机数 Pre-Master .

    再将得到的三个随机数 Client Random Server Random Pre-Master 作为参数, 通过密码套件中的伪随机函数计算得到加密会话的 48 字节的主秘钥 Master Secret .

    再通过 PRF 扩展出 client_write_keyserver_write_key 等会话秘钥.

  4. 客户端再次向服务器发送两个消息:

    • Change Cipher Spec 通知服务器之后改用会话秘钥加密通信
    • Finished 之前的所有握手数据进行摘要加密, 发送给服务器进行确认
    • 使用 ECDHE 实现密钥交换算法时, 此时可以将 HTTP 请求一同发送
  5. 服务器验证握手数据摘要, 并发送消息进行确认:

    • Change Cipher Spec 之后改用会话秘钥加密通信
    • Finished 所有握手数据的摘要
    • 使用 ECDHE 时可一同发送 HTTP 响应
  6. 握手正式结束, 之后收发加密的 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”验证或“一次性票证”.


Profile picture

佚树 的个人博客

关于前端、音乐与生活