https连接建立以及密钥加密详解

1 阅读21分钟

https连接建立以及密钥加密详解

NotebookLM Mind Map.png

一、TCP三次握手

交换https数据之前,客户端和服务端必须先进行TCP三次握手:

  1. 客户端发送SYN
  2. 服务端回复SYN-ACK
  3. 客户端回复ACK

二、TLS握手(开始传输内容的加密工作)

基于RSA密钥交换算法的TLS握手

  1. ClientHello

    • 客户端支持的TLS协议版本
    • 生成的随机数(用于生成会话密钥的条件之一)
    • 客户端支持的密码套件列表:客户端支持的加密算法,如RSA加密算法
  2. ServerHello

    • 确认TLS版本,浏览器不支持则关闭机密通信

    • 服务器生成随机数,后面用于生产会话密钥

    • 确认密码套件列表,确认使用什么加密算法

    • 服务器的数字证书

    Q:数字证书有什么用?为什么要有数字证书?发明数字证书是为了解决什么问题? A:先说一下什么是对称加密和非对称加密:

    • 非对称加密:使用两个密钥,公钥可以任意分发而私钥保密(公钥加密只能用私钥解密,公钥不能解密),可以解决密钥交换问题但速度慢
    • 对称加密:只使用一个密钥,运算速度快,但必须保密,无法做到安全的密钥交换
    • http采用的是混合加密:通信建立前第三步客户端回应采用非对称加密,通信过程中全部使用对称加密的会话密钥加密明文数据

    接下来聊数字证书:前面收到采用非对称加密,这时我们的内容不想被攻击者窃取,所以使用服务器提供的公钥加密,服务器自己用私钥解密,这样攻击者就解密不了,因为只有私钥可以解密,但是我们有个前提必须保障:服务器的公钥怎么到客户端手里,客户端拿到后又怎么保障这个是来自服务器而不是被人偷偷换掉了

    数字证书起的就是一个证明人的作用,服务端的公钥存放在证书中,客户端验证证书的合法性方能拿到公钥。这是数字证书的基本作用,详细后面讲。

  3. 客户端回应

    • 首先通过浏览器或者操作系统中的CA公钥(这个是来验证数字证书的合法性的),确认服务器数字证书的真实性
    • 从数字证书中取出服务器的公钥,然后使用它加密报文,向服务端发送信息:
      • 一个随机数(被公钥加密)

      • 加密通信算法改变通知,随后信息都会使用会话密钥(对称加密)加密

      • 客户端握手结束通知,这一项同时把之前所有内容的发生数据做个摘要(发送整个握手内容中所有消息的哈希值),再用会话密钥加密一下,供服务端校验。如果发现异常,服务端会立即发送一个致命警报(Fatal Alert),并终止握手,双方放弃本次连接。不会尝试修复,直接放弃。

    Q:摘要算法介绍

    A:简单讲就是对传输的内容进行哈希运算计算出哈希值,这个哈希值唯一且不能通过哈希值推导出内容。对方收到后先对内容也计算一个哈希值,进行比较,如果相同说明内容没有被篡改,否则可以判断内容被篡改了。

    上面的第一项的随机数就是整个握手阶段的第三个随机数,服务端和客户端都有了这三个随机数,接着就用双方协商的加密算法,各自生成本次通信的会话密钥。

  4. 服务器的最后回应

    服务器收到客户端的第三个随机数之后,通过协商的加密算法,计算本次通信的会话密钥

    然后向客户端发送最后的消息:

    • 加密通信算法改变通知:表示随后的信息都将用会话密钥加密通信
    • 服务器握手结束后,同时把之前所有的内容的发生的数据做个摘要,用来给客户端校验

至此,整个 TLS 的握手阶段全部结束。接下来,客户端与服务器进入加密通信,就完全是使用普通的 HTTP 协议,只不过用会话秘钥加密内容。

缺陷使用RSA算法最大的问题就是不支持前向保密

因为客户端传递随机数给服务端时使用的是公钥加密,一旦服务端的私钥泄漏了,过去被第三方截获的所有TLS密文都会被破解。为了解决这个问题:后面出现了ECDHE密钥协商算法

基于ECDHE密钥交换算法的TLS握手

ECDHE算法具有前向安全,所以被广泛使用

在这之前,先来推导一下算法的由来,我直接不讲数学,用最通俗方法讲解,有兴趣再去看对应的数学是啥

  1. 离散对数

    一个公式:a^i mod p = b

    指数i称为b的以a为底数的模p的离散对数

    特点是根据从左往右计算容易,但从右往左计算就很难,尤其是p为很大的质数

    //举个例子
    计算3^1000 mod 97 计算机可以快速计算
    //反过来
    已知3^x mod 97 = 某个数,求x
    //计算机只能暴力尝试,不断带入x计算
    

    Q:为什么P要是很大的质数?

    A:安全性取决于可能的取值数量的大小:

    • p为质数:可以取到1-p-1的所有值,只能硬试暴力破解;
    • p为合数,结果只能取到p-1的一部分,攻击空间大幅减小
  2. DH算法

    1. 现假设小红和小明约定使用 DH 算法来交换密钥,那么基于离散对数,小红和小明需要先确定模数和底数作为算法的参数,这两个参数是公开的,用 P 和 G 来代称。
    2. 然后小红和小明各自生成一个随机整数作为私钥,双方的私钥要各自严格保管,不能泄漏,小红的私钥用 a 代称,小明的私钥用 b 代称。
    3. 现在小红和小明双方都有了 P 和 G 以及各自的私钥,于是就可以计算出公钥:
    4. 小红的公钥记作 A,A = G ^ a ( mod P );
    5. 小明的公钥记作 B,B = G ^ b ( mod P );
    6. A 和 B 也是公开的,因为根据离散对数的原理,从真数(A 和 B)反向计算对数 a 和 b 是非常困难的,至少在现有计算机的计算能力是无法破解的
    7. 双方交换各自 DH 公钥后,小红手上共有 5 个数:P、G、a、A、B,小明手上也同样共有 5 个数:P、G、b、B、A。
    8. 然后小红执行运算: B ^ a ( mod P ),其结果为 K,因为离散对数的幂运算有交换律,所以小明执行运算: A ^ b ( mod P ),得到的结果也是 K。

    这个K就是对称加密密钥,可以作为会话密钥使用

    整个过程就公开了4个信息:P,G,A,B,由前面的知识可以知道黑客很难破解,因此DH密钥交换是安全的

  3. DHE算法

    DH算法分为static DH 算法和 DHE算法,前者指双方中一般是服务端的私钥一直是一样的,所以服务端的公钥是不变的,攻击者可以截获海量密钥协商过程的数据暴力破解服务器的私钥,所以static DH 不具备前向安全性;DHE则是双方每次密钥交换通信的私钥都是随机生成的,临时的。每个通信过程中的私钥都是没有任何关系的,都是独立的,这样就保证了前向安全。

Q:什么是前向安全? A:前向安全 是一种通过使用“临时密钥交换”技术,确保长期密钥的泄露不会危及过往会话安全的机制。在监控无处不在、数据存储成本极低的时代,前向安全是确保网络通信“即使未来被攻破,过去依然安全”的核心保障。

  1. ECDHE算法

    DHE算法计算性能不佳,需要做大量乘法,为了提升性能,出现了现在广泛应用于密钥交换的ECDHE算法,本质就是利用ECC椭圆曲线特性,用更少的计算量计算出公钥以及最终的会话密钥。

    • 两人事先确定使用哪种椭圆曲线和曲线上的基点G,这两个参数是公开的
    • 各自随机生成一个随机数作为私钥d,并与基点G相乘得到公钥Q(Q=dG) ×表示点的数乘
    • 双方各自交换,最后小红计算点(x1,y1) = d1Q2,小明计算点(x2,y2) = d2Q1,由于椭圆曲线上是可以满足乘法交换和结合律,所以 d1Q2 = d1d2G = d2d1G = d2Q1 ,因此双方的 x 坐标是一样的,所以它是共享密钥,也就是会话密钥
  2. 接下来我们开始捋一下完整的握手过程:

    1. ClientHello

      客户端使用的TLS版本号

      支持的密码套件列表

      生成的随机数

    2. Server Hello

      确认TLS版本号

      给出一个随机数

      选择一个合适的密码套件:注意!!

      TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 • 密钥协商算法使用 ECDHE; • 签名算法使用 RSA:这里的RSA不用于密钥交换而是用于签名,提供身份认证

      • 握手后的通信使用 AES 对称算法,密钥长度 256 位,分组模式是 GCM;

      • 摘要算法使用 SHA384;


      与上面分开发送Certificate消息,发送证书

      发送完证书后,发送Server Key Exchange消息:

      • 选择椭圆曲线(选好了椭圆曲线相当于椭圆曲线基点G也定好了),公开给客户端
      • 生成随机数作为服务端椭圆曲线的私钥,保留到本地
      • 根据基点G和私钥计算出服务端的椭圆曲线公钥,公开给客户端

      为保证公钥不被第三方篡改,服务端使用RSA签名算法给服务端的公钥做个签名

      注意!!:签名 ≠ 加密

      这里的签名和公钥是两个独立的数据,签名并不是对公钥进行加密,而是通过私钥对其哈希值进行签名,用于保证公钥在传输过程中未被篡改,同时证明该公钥确实来自服务器。


      Server Hello Done 打招呼完毕

    3. 客户端回应

      • 证书校验确认服务端身份
      • 生成一个随机数作为客户端椭圆曲线私钥,再根据前面服务端的信息生成客户端的椭圆曲线公钥,然后用Client Key Exchange消息发给服务端

      至此,双方可以计算出(x,y),其中前面讲x是会话密钥,但在实际应用中,最终的会话密钥是用客户端随机数+服务端随机数+x三个材料生成的,因为TLS设计者不信任客户端 或服务器伪随机数的可靠性,为了保证真正的完全随机,把三个不可靠的随机数混合起来,随机的程度就非常高了,足够让黑客计算不出最终的会话密钥。

      算好会话密钥后,客户端会发一个Change Cipher Spec消息,告诉服务端后续改用对称算法加密通信。

      接着,客户端会发Encrypted Handshake Message消息,把之前发送的数据做一个摘要,再用对称密钥加密一下,让服务端做个验证,验证下本次生成的对称密钥是否可以正常使用。

    4. 服务端回应

      最后,服务端也会有一个同样的操作,发Change Cipher Spec和Encrypted Handshake Message消息,如果双方都验证加密和解密没问题,那么握手正式完成。于是,就可以正常收发加密的 HTTP 请求和响应了。

三、扩展与补充

RSA与ECDHE握手过程的区别

  • RSA 密钥协商算法不支持前向保密,ECDHE 密钥协商算支持前向保密
  • 使用了 RSA 密钥协商算法,TLS 完成四次握手后,才能进行应用数据传输,而对于 ECDHE 算法,客户端可以不用等服务端的最后一次 TLS 握手,就可以提前发出加密的 HTTP 数据,节省了一个消息的往返时间(这个是 RFC 文档规定的,具体原因文档没有说明);
  • 使用 ECDHE, 在 TLS 第 2 次握手中,会出现服务器端发出的Server Key Exchange(沟通ECDHE)消息,而 RSA 握手过程没有该消息;

CA证书认证流程

  1. 首先 CA 会把持有者的公钥、用途、颁发者、有效时间等信息打成一个包,然后对这些信息进行 Hash 计算,得到一个 Hash 值;
  2. 然后 CA 会使用自己的私钥将该 Hash 值加密,生成 Certificate Signature,也就是 CA 对证书做了签名;
  3. 最后将 Certificate Signature 添加在文件证书上,形成数字证书;

客户端校验服务端的数字证书的过程:

  1. 首先客户端会使用同样的 Hash 算法获取该证书的 Hash 值 H1;

  2. 通常浏览器和操作系统中集成了 CA 的公钥信息,浏览器收到证书后可以使用 CA 的公钥解密 Certificate Signature 内容,得到一个 Hash 值 H2 ;

  3. 最后比较 H1 和 H2,如果值相同,则为可信赖的证书,否则则认为证书不可信。

证书信任链

操作系统里一般都会内置一些根证书,我们向CA申请的证书一般不是根证书签发的,而是有中间证书签发的,证书之间有一条链式关系连接,可以类比js的一个事件的全过程。举个例子:

  1. 客户端收到 baidu.com 的证书后,发现这个证书的签发者不是根证书,就无法根据本地已有的根证书中的公钥去验证 baidu.com 证书是否可信。
  2. 于是,客户端根据 baidu.com 证书中的签发者,找到该证书的颁发机构是 “GlobalSign Organization Validation CA - SHA256 - G2”,然后向 CA 请求该中间证书。
  3. 请求到证书后发现 “GlobalSign Organization Validation CA - SHA256 - G2” 证书是由 “GlobalSign Root CA” 签发的,由于 “GlobalSign Root CA” 没有再上级签发机构,说明它是根证书,也就是自签证书。
  4. 应用软件会检查此证书有否已预载于根证书清单上,如果有,则可以利用根证书中的公钥去验证 “GlobalSign Organization Validation CA - SHA256 - G2” 证书,如果发现验证通过,就认为该中间证书是可信的。 “GlobalSign Organization Validation CA - SHA256 - G2” 证书被信任后,可以使用 “GlobalSign Organization Validation CA - SHA256 - G2” 证书中的公钥去验证 baidu.com 证书的可信性,如果验证通过,就可以信任 baidu.com 证书。

Q:为什么需要证书链这么麻烦的流程?Root CA 为什么不直接颁发证书,而是要搞那么多中间层级呢?

  1. Root CA 私钥是整个信任体系的命脉,频繁用于颁发证书会增加泄露风险
  2. 中间证书出现问题 只需要处理中间证书,如果是直接办法证书出现问题就要直接换它,所有设备都必须更新信任列表,这是巨大的灾难

TLS1.2与TLS1.3比较

TLS1.3比较于TLS1.2,将密钥交换前置到握手初期,并在服务端问候后立即进入加密通信,精简握手消息并强制使用具备前向安全的密钥交换算法。

阶段TLS1.2 做什么TLS1.3 做什么本质区别 / 优化点
客户端问候支持的协议版本- 加密套件列表- 客户端随机数支持的协议版本- 精简的加密套件- 客户端随机数 直接带上椭圆曲线公钥TLS1.3 一开始就发送公钥,为后面提前生成密钥做准备
服务端问候选择协议版本- 选择加密套件- 服务端随机数选择协议版本- 选择加密套件- 服务端随机数 返回自己的椭圆曲线公钥TLS1.3 在这里就能完成密钥交换
密钥交换阶段还未完成,需要后续步骤已完成(双方已能算出共享密钥)TLS1.3 密钥生成更早
服务端密钥交换消息有- 椭圆曲线公钥- 用证书私钥做签名------TLS1.3 减少消息数量,结构更简洁
证书传输明文发送证书加密后发送证书TLS1.3 保护隐私,避免被窃听分析
签名验证阶段在“服务端密钥交换”中完成单独通过“证书验证”消息完成TLS1.3 职责更清晰(认证与密钥交换分离)
服务端结束标志有(通知客户端服务端发完了)没有TLS1.3 删除冗余步骤
客户端密钥交换客户端再发送自己的公钥不需要(前面已经发过)TLS1.3 减少一次通信
开始加密通信很晚(在“切换加密状态”之后)很早(服务端问候之后就开始)TLS1.3 握手中后半段全部加密
切换加密状态必须存在基本废弃(只为兼容)TLS1.3 流程简化
完成握手验证最后才验证所有握手数据全程逐步验证(且已加密)TLS1.3 安全性更强
握手往返次数需要 2 次往返(2 RTT)只需 1 次往返(1 RTT)甚至支持 0 RTTTLS1.3 明显更快

补充:TLS 1.3 的 0-RTT 握手是其最重要的性能优化之一。它允许客户端在完全建立加密连接之前(即在第一个数据包中)就向服务器发送应用层数据(如 HTTP 请求)。0-RTT 不能发生在客户端与服务器的第一次连接中。它必须基于之前已经建立过的连接:

  • PSK(Pre-Shared Key): 在第一次 TLS 1.3 握手(1-RTT)成功后,服务器会发送一个 NewSessionTicket 消息给客户端。
  • 这个 Ticket 包含了一个 PSK(预共享密钥)或者可以推导出 PSK 的信息,以及该 Ticket 的有效期等元数据。
  • 客户端会将这个 PSK 存储在本地,用于后续的快速重连。
  1. 客户端发送(第 1 阶段):
    • 客户端在发送 ClientHello 的同时,附带 early_data 扩展。
    • 客户端利用之前存储的 PSK 导出“早期密钥”(Early Secret)。
    • 关键点: 客户端直接使用这个早期密钥加密 HTTP 请求(例如 GET /index.html),并随 ClientHello 一起发送给服务器。
    • 此时,客户端还没收到服务器的任何回复。
  2. 服务器处理(第 2 阶段):
    • 服务器收到 ClientHello,识别出 PSK。
    • 服务器使用相同的 PSK 导出“早期密钥”,解密并处理随附的加密数据(Early Data)。
    • 服务器发送 ServerHello、完成握手,并直接返回应用层响应数据(如 HTML 内容)。
  3. 结果: 客户端在发出第一个包后,下一个收到的包里可能就包含了业务数据。从应用层角度看,延迟为 0 个往返时延(RTT)。

但是这样存在重放攻击:举个攻击场景:

  • 第一步(截获): 客户端在咖啡厅连 WiFi 发送了一个 0-RTT 请求(比如:GET /buy_ticket)。攻击者通过监听网络,把这个二进制数据包原封不动地录下来。
  • 第二步(重放): 客户端下线后,攻击者把这个数据包再次发给服务器。
  • 第三步(服务器被骗): 服务器收到后,发现 PSK 是对的,解密出来的加密请求也是完整的。服务器并不知道这是攻击者发来的旧包,以为客户端又发起了一次同样的请求。

TLS1.3的风险与性能平衡策略

  1. 服务器端抗重放检测: 服务器记录最近收到的 0-RTT 请求的“指纹”。如果短时间内看到重复的指纹,就拒绝它。但这需要大量内存。
  2. 限制业务场景: 就像之前提到的,只允许**幂等(相同输入都得到相同输出)(GET)**操作。如果攻击者重放了一个“看新闻”的请求,由于看一次和看十次结果一样,重放也就失去了攻击价值。
  3. 过期时间: 强制 0-RTT 的 Ticket(PSK)在很短时间内失效。

TLS记录协议过程(非握手协议过程)

TLS 记录协议负责保护应用程序数据并验证其完整性和来源,所以对 HTTP 数据加密是使用记录协议;

  • 首先,消息被分割成多个较短的片段,然后分别对每个片段进行压缩。
  • 接下来,经过压缩的片段会被加上消息认证码(MAC 值,这个是通过哈希算法生成的),这是为了保证完整性,并进行数据的认证。通过附加消息认证码的 MAC 值,可以识别出篡改。与此同时,为了防止重放攻击,在计算消息认证码时,还加上了片段的编码。
  • 再接下来,经过压缩的片段再加上消息认证码会一起通过对称密钥进行加密。
  • 最后,上述经过加密的数据再加上由数据类型、版本号、压缩后的长度组成的报头就是最终的报文数据。

记录协议完成后,最终的报文数据将传递到传输控制协议 (TCP) 层进行传输。


!! 以上是过程是以前的做法,存在一定的安全风险:

  1. 采用MAC+CBC(一种对称加密的工作模式),会导致Padding Oracle Attack,Lucky13等攻击,具体大家自己了解,但本质就是因为CBC模式需要填充(要求明文长度是块大小的整数倍,块指的是分组密码在进行加密运算时一次性处理的数据单元),解密时先校验填充后校验MAC,攻击者可以利用漏洞不断进行尝试,这里我们以Lucky13进行攻击例子:
    1. 攻击者发送一个精心修改的密文。
    2. 服务器处理时:
      • 如果填充错误,它会立即返回错误,不计算MAC。
      • 如果填充正确,它需要先移除填充,再计算MAC(计算MAC需要遍历整个数据,时间更长)。
    3. 攻击者通过精确测量响应时间:时间短 = 填充错误时间长 = 填充正确
    4. 通过几万次这样的时序试探,攻击者就能像猜密码一样,一字节一字节地解密出整个会话的Cookie、密码等敏感信息。
  2. 解决方法:采用更先进的AEAD机制
    1. 一次性完成认证和加密,减少实现错误

    2. CBC模式需要填充,AEAD不需要填充,根本上移除了填充相关的攻击面

    3. AEAD 是 先验证,后解密。CBC+MAC 是 先解密,后验证。先验证意味着攻击者连触发解密的机会都没有,也就无法通过操纵解密过程中的中间状态来获取信息。

      • CBC+MAC 模式:它的验证路径是 明文依赖型

        1. 先解密密文 -> 得到明文

        2. 检查明文中的填充是否合法 -> 这个检查完全依赖于解密后的明文内容。

          填充字符和明文字符之间确实存在这种可被利用的数学关联,这正是 Padding Oracle Attack 能够成功破解明文的根本原因

        3. 如果填充合法,再对明文计算 MAC。

        4. 攻击者正是利用了“填充是否合法”这个依赖于明文且容易出错的步骤,通过观察服务器反应(错误信息或时间)来逐步猜解明文。解密过程与明文内容产生了条件分支(例如,if 填充合法 then 做某事)。

      • AEAD 模式:它的验证路径是 密文+标签依赖型

        1. 同时使用密文、附加数据(AAD)、密钥和随机数(Nonce)来计算一个预期的认证标签

        2. 将这个预期标签与通信中附带的实际标签进行比较。

        3. 只有在标签验证完全通过之后,才会进行解密操作。

        4. 攻击者无法操纵“填充”来创造不同的状态,因为根本没有填充。验证过程只有一个分支:标签对还是错?