HTTPS\TLS协议过程的详细补充

HTTPS\TLS协议过程的详细补充

整个抓包的流如下:

在这里插入图片描述

包含完整的HTTPS请求的过程:

  • TCP 三次握手
  • 使用 TLSv1.2 进行 SSL 握手
  • 使用握手协商好的密钥对 HTTP 进行加密传输
  • TCP 四次挥手

接下来逐包进行分析:

定义A端为172,B端为49

1、TCP三次握手

准备阶段:

A主动打开连接,B被动打开连接,但是这之前B的TCP服务器进程先创建传输控制块TCB,准备接收客户端进程的连接请求,然后服务器进程处去LISTEN(侦听)状态,等待客户端连接请求。

第一次握手:

A的TCP客户进程也是首先幻剑传输控制模块TCB,然后发出链接请求报文段,这时首部中的同步位SYN=1,同时选择一个初始序列好seq=x。这里seq = 0 。 TCP规定SYN报文段不能携带数据,但要消耗掉一个序号,这时TCP客户进程进入SYN-SENT状态。

A 发出一个带 SYN 同步位的包,通知服务端要建立连接。第一次握手,发出的 TCP 包的数据和 Wireshark 解析的结果如下:

在这里插入图片描述

 0  1  2  3  4  5  6  7  8  9  10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|                    源端口                      |                     目的端口                   |
+-----------------------------------------------+-----------------------------------------------+
|                                             序列号                                             |
+-----------------------------------------------------------------------------------------------+
|                                             确认号                                             |
+-----------+-----------------+-----------------+-----------------------------------------------+
|  首部长度  |     保留位       | U| A| P| R| S| F|                      窗口                      |
+-----------+-----------------+-----------------+-----------------------------------------------+
|                    校验和                      |                     紧急指针                   |
+-----------------------------------------------+-----------------------+-----------------------+
|                    选项                                                |        填充            |
+=======================================================================+=======================+
|                                             数据                                               |

参考谢希仁版本的 《计算机网络》一书,对照着整个报文格式表,把整个 TCP 报文的二进制信息和相关意义做些说明:

  • 源端口,2 字节,0xbda2 = 48546。

  • 目的端口,2字节,0x01bb = 443。

  • 序号,4 字节,0xa1d05d1e。TCP 连接传送的每一个字节都有编号,而这里表示的是要发送的数据(data)的第一个字节的序号。后面按照这个序号递增。这里发送的是 TCP 连接的第一个包,这里的序号是随机产生的。

  • 确认号,4 字节,0x00000000。期望收到的下一个报文段数据部分第一个字节的序号。如果发出了 N 确认号,就表示 N-1 之前的数据都收到了。

  • 首部长度(偏移),4 位,0xa = 10。这里每一位表示4字节,所以 10*4 = 40 字节。TCP 首部的长度,因为 4 位最大的数为 15,所以整个首部最大 60 字节。首部后面就是数据字段。

  • Flag , 0x002 , 这里共12位如下保留位和控制位

    保留位,6 位,二进制 000000。这里还没有用到为 0,这里的设计作为一个扩展以便未来会用上。

    控制位,6 位, 二进制 000010。每一位都有意义,这里对应 6 种类型:

  • URG,Urgent,表示紧急数据要提交,有了这个标记为整个报文就有了插队的特权,和紧急指针一起用。
    ACK,Acknowledge,1 表示这是一个确认报文,用来确认收到了包,确认报文是不带数据的。
    PSH,Push,1 表示这是一个推送报文,通知对方尽快响应。因为服务端可能因为缓存问题要等了一会儿才发包。这里就是催促一下对方赶紧发包。
    RST,Reset,1 表示拒绝了这个包,网络发生错误的时候这种包会非常多。比如重复的包就会被 reset 掉。
    SYN,Synchronization,1 表示建立连接用来同步序号。用来握手阶段,通知对方包的初始序号。
    FIN,Finish,1 表示发送方 B 完成数据发送,通知接收方 A 该结束了。
    

    我们这里作为整个请求过程中的第一个 TCP 报文,仅仅设置一个控制位 SYN,一方面通知响应者 B 要建立连接了,另一方面同步一下序号。

  • 窗口,2 字节,0x7210 = 29200。发送本报文的接收窗口大小,比如 A 发送了这个报文,表示能接收的数据量为从确认号算起来加上窗口大小,B 发送的报文字节数不能超过这个限制。这个值受 A 的缓存影响,是动态变化的。前面给出确认号为 0, 然后窗口大小 29200,表示还有 29200 的缓存空间可以接受序号 0 ~ 29200 的字节。

  • 检验和,2 字节,0x4e4d。接收方受到报文要计算一下数据包的检验和是否和该值匹配,确保数据包的完整。这里只保证了数据完整性,并没有确认服务端身份,也没有用摘要算法。目的在于能快速检验,而且采用检验和的方式还可以使用硬件加速。

  • 紧急指针,2 字节,0x0000。和控制位 URG 配合使用,意义在于,有紧急数据要处理,这里的值表示紧急数据在报文中的位置。

到这里的话,TCP 数据报首部固定部分结束,固定部分一共有 20 字节。也就是 TCP 首部,至少要有 20 字节。

固定首部后,就是可长度可以变化的选项了:

选项,可达 40 字节

  • 最大报文长度,0x020405b4。0x02 表示这是个 MSS 选项,0x04 表示该选项一共有 4 字节,这里的 0x05b4 = 1460 为该选项的值。这个表示 TCP 数据部分的最大字节数。

  • SACK permitted , 0x0402 SACK是一个TCP的选项,来允许TCP单独确认非连续的片段,用于告知真正丢失的包,只重传丢失的片段。

  • 时间戳,0x080a0045063100000000。0x08 表示这是个时间戳选项,0x0a 表示该选项一共有 10 字节,0x00f3ee = 4523569 就是发送者的发送时间,0x00000000 = 0 表示接收端的时间。

  • NOP , 0x01, 这个字段实际上是没有任何意义的字段。设计该字段主要是用来提供填充垫片。TCP的头部必须是4byte的倍数,但是大多数的TCP选项不是4byte的倍数。假如出现了整个TCP选项部分不是4byte的倍数,那么就需要使用1或多个字节无意义的nop 来填充,使之符合TCP的头部构造的规定。例如,选项部分只有6byte ,2个字节的nop就会用来做垫片。 NOP也会做为分割不同的选项数据,例如我们的TCP会话中同时使用了窗口扩大选项和SACK,那么TCP头部中的SACK的表示部分与窗口扩大选项中的参数之间使用nop隔离。明确不同的选项之间的分割点,因此在一个数据包中出现多个nop 是不奇怪的。

  • Window scale , 0x030306 ,在TCP的报头中,有一个16bit的字段是表示window的大小的,而2^16 = 65536,即由于字段的长度,窗口最大为64KB,显然对于现在动辄1000Mbps的带宽,这个大小是远远无法满足实际需求。如果要修改该字段的长度,则对TCP的兼容性会造成比较大的影响,因此采用了Window scale的方法。即在TCP的报头的option 中增加一个Window scale,计算方式如下:
    实际的窗口大小 = (window size value) X (2^(Window scale))

    这里实际大小 = 29200 * 2^6

第二次握手:

B收到连接请求报文,如果同意建立连接,则向A发送确认。在确认报文段中应该把SYN和ACK都置1,确认号是 ack = x+1 ,同时也为自己选择一个初始序列号 seq=y ,这个报文也不能携带数据,并且消耗一个序号 ,这是TCP服务器进程进入SYN-RCVD状态。

B 收到 SYN 包,发出 SYN + ACK 确认包

这个包,既是确认收到了第一次握手的包,也是一个由 B 端发出的同步包,表示自己准备好了,可以开始传数据了。

因为 Wireshark 已经帮我们分析好包的内容了,上面列举的包二进制数据和 TCP 报文结构只是为了学习,实际应用可以直接看 Wireshark 的解析内容。包的内容如下:

在这里插入图片描述

看一下区别

窗口大小:0xffff , 65535

控制位:既有 SYN 又有 ACK。因为这既是一个接收端 B 的自己同步包,里面有一个接收端的初始序号,Wireshark 转化为相对序号 0;同时这也是对第一次握手的包的确认。因此这个包也不带数据。

控制位:既有 SYN 又有 ACK。因为这既是一个接收端 B 的自己同步包,里面有一个接收端的初始序号,Wireshark 转化为相对序号 0;同时这也是对第一次握手的包的确认。因此这个包也不带数据。

可以看到,这个包的应答时间戳刚好是第一次握手的发送时间戳。从这里也可以理解到,这个包就是在响应第一次握手的包

所以,接收方 A 可以利用这个值来计算这一次 RTT,收到第二次握手的包后,计算当前时间戳减去该包的应答时间戳就是一个 RTT 的延时了。

第三次握手:

TCP客户进程收到B给出的确认。确认报文段的ACK置1,确认号ack = y+1,而自己的序列号seq=x+1 。TCP标准规定ACK可以鞋待数据,但如果不携带数据则不消耗序列号,在这种情况下,下一个数据报文段的序号仍是seq=x+1 。这时TCP已经建立连接 , A进入ESTABLISHED状态,当B收到A的确认后也进入ESTABLISHED状态

A 收到后,再发出一个 ACK 确认包

这是为什么呢?

假设我们用两次握手,然后在第一次握手期间,A 发了第一次握手包后出现了这样的场景:一直没有得到响应而进行超时重传,又发了一次包,然后我们称上一次包为失效包。

然后我们可以看到:

新包到达接收端 B,然后因为两次握手 B 觉得连接建立,于是等着发送端 A 发数据,A 也发了数据。
失效的包经过艰苦跋涉,也到达了接收端 B,B 并不清楚这是失效包,又开始等待发送端 A 发数据。然而此时 A 不发数据,所以 B 的资源就浪费掉了。

所以,只有接收端 B 在发送端 A 发出了第三次握手包后,才认为连接已经建立,开始等待发送端 A 发送的数据,才不会因为失效的连接请求报文导致接收端异常。

总结

三次握手,有几个重要的任务,一个是同步序号,接收端和发送端都发出同步包来通知对方初始序号,这样子后面接收的包就可以根据序号来保证可靠传输;另一个是让发送端和接收都做好准备。然后就开始传数据了。

2、TLS/SSL 握手

SSL 整个协议实际上分两层,SSL 记录协议和其他子协议(SSL握手协议,SSL改变密码协议,SSL警告协议):

这两层协议的关系,其实就是数据封装的关系,SSL记录协议封装其他上层协议。

封装握手Handshake协议:

Transport Layer Security
    TLSv1.2 Record Layer: Handshake Protocol: Client Hello
        Content Type: Handshake (22)
        Version: TLS 1.0 (0x0301)
        Length: 153
        Handshake Protocol: Client Hello

封装应用数据协议,比如 HTTP:

Transport Layer Security
    TLSv1.2 Record Layer: Application Data Protocol: http-over-tls
        Content Type: Application Data (23)
        Version: TLS 1.2 (0x0303)
        Length: 1072
        Encrypted Application Data: 6d9b3c9089271630c33506fe28cd6a61fed1f4bd2808f537...

封装交换密码协议:封装警报协议:等等把

所以 SSL 记录协议其实就是一个其他协议的载体,只是提供了一个封装的功能。

记录协议的主要目的有这几个,为其他 SSL 子协议提供了以下服务:

  • 分组、组合。如果有几个协议的内容,是可以不用等客户端确认后再发送的,这里会进行组合合并,然后在同一个 TCP 包中发送。接收方接收到组合的 SSL 记录协议报文,也会根据协议来进行分组。

  • 压缩、解压缩。可选项,如果需要压缩的话。

  • 消息认证(MAC)。注意,这里不提供数字签名。消息认证用的是用对称密钥,解密算法效率比公钥算法快,安全性弱一些。如果已经明确了,加密用的对称密钥只有合法的客户端和服务端获得,那么这个的安全效果和数字签名一样。

  • 加密传输 。比如已经协商好加密密钥和算法了,直接对应用层协议加密传输

TCP 三次握手结束并且和代理服务器成功连接后,建联成功,客户端 A 就开始发起 SSL 连接,首先会进入 SSL 握手阶段。

SSL 握手阶段的主要目的有这么几个:

  • 协商加密算法。为了能够提供效率,使用对称密钥。对称加密使用的是位运算,速度快,甚至可以硬件加速。非对称加密比如 RSA,使用了大数乘法等,整体会比较慢。对称加密只要密钥没有泄漏,那也是非常安全的。这也是后面 SSL 握手协议要确保的。

  • 协商加密密钥 。用来对后面的 HTTP 协议等应用协议内容进行加密。这个密钥又称为主密钥,为加密算法的密钥。

  • 验证身份 。通常情况下,只要验证服务端身份。特殊情况下,比如一些安全级别高的应用场景,还要验证客户端身份。服务端会返回证书链,有根 CA 证书在里头。通过证书的链式担保,可以确认服务端是否是可信任的。同时,在握手期间,公钥传输成功后,还会对某些信息进行数字签名,确保数据没有被篡改且身份无误。

SSL 握手的流程并不是一成不变的,根据实际的应用场景来。主要有三种:

  • 只验证服务端。这个用三个阶段就完成握手,我们这次的请求也是这样。一般的网络请求也仅仅到这个程度。
  • 验证服务端和客户端。在安全性要求较高的场景,服务端也要验证客户端的身份。方式也是发证书证明自己。
  • 恢复原有会话 。这个属于HTTPS 优化的范畴。使用 Session Ticket 或者 Session ID 机制恢复之前已经完成握手的会话。这个是可以允许在不同的 TCP 上进行的。因为握手的加密数据已经保存,直接恢复就可以开始传递了。Session Ticket 由客户端保存加密信息,Session ID 的方式由服务端保存加密信息。不过 Session Ticket 在 Android 客户端还没有得到广泛的支持,和具体机型和内置的 OpenSSL 的版本有关。

SSL 握手的完整的交互过程如下,这里是验证服务端又验证了客户端的情况:

在这里插入图片描述

我们的请求只验证服务端,所以 7,8,9 是不存在的。

现在具体分析每一个阶段的内容。

阶段一Client Hello

作为 SSL 握手的第一个握手包,我们详细分析和理解一下包的内容。

Transport Layer Security
    TLSv1.2 Record Layer: Handshake Protocol: Client Hello
        Content Type: Handshake (22)
        Version: TLS 1.0 (0x0301)
        Length: 171
        Handshake Protocol: Client Hello
            Handshake Type: Client Hello (1)
            Length: 167
            Version: TLS 1.2 (0x0303)
            Random: 2f0a96a1416f4495997b6cd5083d84fde5f05fbf3ef5be5ef5a24dd0742022be
            Session ID Length: 0
            Cipher Suites Length: 12
            Cipher Suites (6 suites)
            Compression Methods Length: 1
            Compression Methods (1 method)
            Extensions Length: 114
            Extension: renegotiation_info (len=1)
            Extension: server_name (len=38)
            Extension: extended_master_secret (len=0)
            Extension: session_ticket (len=0)
            Extension: signature_algorithms (len=22)
            Extension: application_layer_protocol_negotiation (len=11)
            Extension: ec_point_formats (len=2)
            Extension: supported_groups (len=8)
  • 随机数 ,生成一个32字节随机数。最后加密数据用的主密钥,需要客户端和服务端一起协商出来。后面服务端的 Server Hello 阶段也会生成一个随机数。一同用来计算出主密钥。
  • 会话ID ,这里为 0。这个 Session ID 是可以重用的,具体看服务端资源和支持情况。如果要复用 Session ID, SSL 服务端需要维护连接的状态和上次握手成功留下的加密信息。因为这是这是第一次访问该网址,会话 ID 尚未创建,客户端没记录,这里为 0。如果客户端保存了 Session ID 的信息,下次发起 SSL 请求的时候会带上。
  • 加密套件 ,客户端可以支持的密码套件列表。这些套件会根据优先级排序。每一个套件代表一个密钥规格。以 “TLS” 开头,接着是密钥交换算法,然后用 “WITH” 连接加密算法和认证算法。一个加密套件有这么几个内容:密钥交换算法、加密算法(会带有支持的最高密钥位数)、认证算法还有加密方式

密钥交换算法用在 SSL 握手阶段的交换协商好的对称密钥的阶段,为非对称加密,比如:

  • EC Deffie-Hellman 密钥交换算法。这里会被缩写为 ECDHE,也称为 DH 加密。
  • RSA 密钥加密算法。

加密算法,是最后要用来加密 HTTP 数据的,为对称加密算法,比如:

  • DES
  • 3DES
  • AES

摘要算法,也是对数据进行摘要。后面可以用来做数据的校验,保证数据的一致性,让中途被篡改的包失效,比如:

  • MD5
  • SHA1
  • SHA256

所以这里一共应用了三种密钥技术,非对称密钥,对称密钥和摘要算法。用一句话总结:用非对称加密算法来传递对称加密算法的密钥,同时用摘要算法保证数据的完整性。

这个客户端支持6个加密套件

Cipher Suites (6 suites)
    Cipher Suite: TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 (0xc02b)
    Cipher Suite: TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 (0xc02c)
    Cipher Suite: TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 (0xc02f)
    Cipher Suite: TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (0xc030)
    Cipher Suite: TLS_DHE_RSA_WITH_AES_128_GCM_SHA256 (0x009e)
    Cipher Suite: TLS_DHE_RSA_WITH_AES_256_GCM_SHA384 (0x009f)
  • 压缩算法 ,这里为 0,说明不支持压缩算法
  • 扩展字段 ,一些扩展信息,比如 SNI 的支持,ALPN 的信息等等。
阶段二Server Hello

服务端收到了客户端的 Hello,通过客户端的配置信息,结合服务端的自身情况,给出了最终的配置信息。

Wireshark 解析后的内容如下:

Transport Layer Security
    TLSv1.2 Record Layer: Handshake Protocol: Server Hello
        Content Type: Handshake (22)
        Version: TLS 1.2 (0x0303)
        Length: 80
        Handshake Protocol: Server Hello
            Handshake Type: Server Hello (2)
            Length: 76
            Version: TLS 1.2 (0x0303)
            Random: b7b569f52c92a86c13de412478ea1d646dc3a20bed5a836da1082275f0f9beb6
            Session ID Length: 0
            Cipher Suite: TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (0xc030)
            Compression Method: null (0)
            Extensions Length: 36
            Extension: renegotiation_info (len=1)
            Extension: ec_point_formats (len=4)
            Extension: session_ticket (len=0)
            Extension: extended_master_secret (len=0)
            Extension: application_layer_protocol_negotiation (len=11)
  • 随机数 ,上面的 Client Hello 过程也生产了一个 32 位随机数,这两个随机数将参与主密钥(master key)的创建。

  • 会话ID ,这里还是0,如果不为 0。说明服务端允许客户端再以后的 SSL 连接中复用这次会话。在使用 HTTPS 的 Session Ticket 可以用到。会话 ID 由服务端维持,采用恢复会话的方式创建 SSL 连接。

  • 加密套件Cipher Suite: TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (0xc030) 。这个是从客户端 Client Hello 上传的 6 个加密套件中选中的,根据密码套件的格式,上面的信息有,交换加密算法为ECDHE ,就是EC Diffie-Hellman ,RSA 表示后面 Server Key Exchange 阶段的携带 DH 加密算法的公钥的包的数字签名的加密算法是 RSA。

    加密算法为 AES ,最高密钥支持 256 位,使用 GCM 分组。

    认证算法 SHA 。所谓 GCM 就是 AES 的机密模式,对称加密采用Counter模式

    ECDHE_RSA,表示交换加密算法为ECDHE ,RSA 是后面的 获取 ECDHE 的参数的包进行的数字签名用的算法。

阶段三Certificate

上面的 Server Hello 已经制定了接下来的非对称加密算法

服务端下发证书,客户端验证服务端的身份,并且取出证书携带的公钥,这个公钥是交换加密算法的公钥。也就是在 Server Hello 阶段指定的 ECDHE (EC Diffie-Hellman)算法,也是通常说的 DH 加密。

这个 Certificate 消息下发了从携带自己公钥的数字证书和 CA 证书的证书链,在 Certificates 字段中:

Transport Layer Security
    TLSv1.2 Record Layer: Handshake Protocol: Certificate
        Content Type: Handshake (22)
        Version: TLS 1.2 (0x0303)
        Length: 2944
        Handshake Protocol: Certificate
            Handshake Type: Certificate (11)
            Length: 2940
            Certificates Length: 2937
            Certificates (2937 bytes)
                Certificate Length: 1825
                Certificate: 3082071d30820605a003020102020c67057452eb1bd4cc9f16a8b7300d06092a864886f7… (id-at-commonName=datacollector.drru.dt.hicloud.com,id-at-organizationName=Huawei Software Technologies Co., Ltd.,id-at-organizationalUnitName=Cloud P
                Certificate Length: 1106
                Certificate: 3082044e30820336a003020102020d01ee5f221dfc623bd4333a8557300d06092a864886… (id-at-commonName=GlobalSign RSA OV SSL CA 2018,id-at-organizationName=GlobalSign nv-sa,id-at-countryName=BE)

CA 是 PKI 体系的重要组成部分,称为认证机构。

那什么是 CA 证书?就是用来 CA 中心发布的,认证该服务单证书的合法性,可以确保该证书来源可靠而不是被中间人替换了。但是 CA 证书也可能被中间人拦截造假?那就再用一个证书来认证它。看起来好像没完没了。实际上到最后有一个根 CA 证书,这个证书存储再浏览器或者操作系统中,是系统直接信任的。

服务端证书需要 CA 证书做认证。使用的还是数字签名方式,从数据中摘要一段信息,用 CA 证书的加密。然后验证的时候时候,用 CA 证书的公钥解密,用同样的摘要算法摘要数据部分和解密好的信息进行比较。

在这里插入图片描述

客户端在验证服务端证书的有效性有这样的一个过程。首先会找到该证书的认证证书,也就是中级 CA 证书。然后找中级 CA 证书的认证证书,可以是另一个中级 CA 证书,也可能是根 CA 证书。这样直到根 CA 证书。

接着从根 CA 证书开始往下去验证数字签名。比如有这样的证书链:根 CA 证书-> 中级 CA 证书 -> 服务端证书。用 CA 证书的公钥去验证中级证书的数字签名,再用中级证书的公钥去验证服务器证书的数字签名。任何一个环节验证失败,就可以认为证书不合法。

这就是整个证书链的认证过程:

在这里插入图片描述

查看抓到的包的数据,发现只有两个证书。为服务端证书和中级 CA 证书。根 CA 证书呢?顺藤摸瓜找到它。

首先看服务端证书。它内容如下:

Certificate: 3082071d30820605a003020102020c67057452eb1bd4cc9f16a8b7300d06092a864886f7… (id-at-commonName=datacollector.drru.dt.hicloud.com,id-at-organizationName=Huawei Software Technologies Co., Ltd.,id-at-organizationalUnitName=Cloud P
    signedCertificate
        version: v3 (2)
        serialNumber: 0x67057452eb1bd4cc9f16a8b7
        signature (sha256WithRSAEncryption)
            Algorithm Id: 1.2.840.113549.1.1.11 (sha256WithRSAEncryption)
        issuer: rdnSequence (0)
            rdnSequence: 3 items (id-at-commonName=GlobalSign RSA OV SSL CA 2018,id-at-organizationName=GlobalSign nv-sa,id-at-countryName=BE)
                RDNSequence item: 1 item (id-at-countryName=BE)
                RDNSequence item: 1 item (id-at-organizationName=GlobalSign nv-sa)
                RDNSequence item: 1 item (id-at-commonName=GlobalSign RSA OV SSL CA 2018)
        validity
            notBefore: utcTime (0)
            notAfter: utcTime (0)
        subject: rdnSequence (0)
            rdnSequence: 6 items (id-at-commonName=datacollector.drru.dt.hicloud.com,id-at-organizationName=Huawei Software Technologies Co., Ltd.,id-at-organizationalUnitName=Cloud Platform CNDR Dept,id-at-localityName=Nanjing,id-at-stateOrProvinceNa
                RDNSequence item: 1 item (id-at-countryName=CN)
                RDNSequence item: 1 item (id-at-stateOrProvinceName=Jiangsu)
                RDNSequence item: 1 item (id-at-localityName=Nanjing)
                RDNSequence item: 1 item (id-at-organizationalUnitName=Cloud Platform CNDR Dept)
                RDNSequence item: 1 item (id-at-organizationName=Huawei Software Technologies Co., Ltd.)
                RDNSequence item: 1 item (id-at-commonName=datacollector.drru.dt.hicloud.com)
        subjectPublicKeyInfo
            algorithm (rsaEncryption)
            subjectPublicKey: 3082010a0282010100822977f9f0ca28c9055917a65eae7935d47ff039c01c19a5e589f7…
        extensions: 10 items
            Extension (id-ce-keyUsage)
            Extension (id-pe-authorityInfoAccess)
            Extension (id-ce-certificatePolicies)
            Extension (id-ce-basicConstraints)
            Extension (id-ce-cRLDistributionPoints)
            Extension (id-ce-subjectAltName)
            Extension (id-ce-extKeyUsage)
            Extension (id-ce-authorityKeyIdentifier)
            Extension (id-ce-subjectKeyIdentifier)
            Extension (SignedCertificateTimestampList)
    algorithmIdentifier (sha256WithRSAEncryption)
        Algorithm Id: 1.2.840.113549.1.1.11 (sha256WithRSAEncryption)
    Padding: 0
    encrypted: 092fe83450a43e73eb14ec16c2119d9c3c4e93d0d61143070049fd28098b33da27bbf9cb…

从这个证书中我们可以窥见这些信息:

首先是 signedCertificate 字段的内容,即数字证书的数据:

  • 版本,version,v3。对应的就是 X.509 V3 标准。
  • 序列号 ,serialNumber,0x0x67057452eb1bd4cc9f16a8b7,证书颁发者唯一序列号。
  • 签名算法ID ,Signature Algorithm。这里指的是使用 SHA-256 进行摘要,RSA 进行加密的签名算法。
  • 证书颁发者 ,issuer,就是颁发该证书的 CA 的信息。里面携带后该 CA 的唯一名称(DN,Distinguished Name),比如国家为 US(美国),组织机构为 GeoTrust Inc.,名称为 GeoTrust SSL CA - G3。后面我们需要从证书链找到该 CA 证书,去认证当前证书。
  • 有效期 ,validity,证书的起始时间和终止时间。可以得出该证书到 21 年 10月 21 日后过期。
  • 对象名称 ,subject,里面就是该证书的名称等主要信息了。比如国家为 CN(中国),组织为 Huawei Software Technologies Co., Ltd.。名称为 *.hicloud.com ,也是该证书适用的域名。
  • 对象公钥信息 ,subjectPublicKeyInfo。因为这是服务端证书,这个公钥后面将用于主密钥的交换过程,从中可以了解到这个公钥采用 RSA 加密,公钥内容为 3082010a0282010100822977f9f0ca28c9055917a65eae7935d47ff039c01c19a5e589f7…
  • 扩展部分 ,一些扩展信息。比如对象的别名。这个如果是 CDN 的服务器证书,那么别名将会非常多。是所有使用该 CDN 的网站的域名。比如之前抓到的 CDNetworks 的 CDN 证书,它的扩展字段 subjectAltName 有这些信息:

然后是证书颁发机构的签名信息:

  • 签名算法,algorithmIdentifier。这里得出使用的还是 SHA-256 摘要加 RSA 加密的签名算法。这个就是认证该证书的 CA 证书使用的签名算法。
  • 签名信息 ,encrypted,这个信息的内容,CA 证书对 SHA-256 对上面的数据部分进行摘要后,使用 RSA 的私钥加密获得。后面会用在该证书的认证过程,取出 CA 证书的公钥,解密签名信息,用同样的算法获取数据摘要,对比一下是否相同。

从上面的 issuer 可以了解到,认证该服务器证书的 CA 证书为 GlobalSign RSA OV SSL CA 2018 ,我们从 Certificates 找到对应的中级证书的内容如下(中级证书可以有好几级,我们这儿只有一级):

Certificate: 3082044e30820336a003020102020d01ee5f221dfc623bd4333a8557300d06092a864886… (id-at-commonName=GlobalSign RSA OV SSL CA 2018,id-at-organizationName=GlobalSign nv-sa,id-at-countryName=BE)
    signedCertificate
        version: v3 (2)
        serialNumber: 0x01ee5f221dfc623bd4333a8557
        signature (sha256WithRSAEncryption)
            Algorithm Id: 1.2.840.113549.1.1.11 (sha256WithRSAEncryption)
        issuer: rdnSequence (0)
            rdnSequence: 3 items (id-at-commonName=GlobalSign,id-at-organizationName=GlobalSign,id-at-organizationalUnitName=GlobalSign Root CA - R3)
                RDNSequence item: 1 item (id-at-organizationalUnitName=GlobalSign Root CA - R3)
                RDNSequence item: 1 item (id-at-organizationName=GlobalSign)
                RDNSequence item: 1 item (id-at-commonName=GlobalSign)
        validity
            notBefore: utcTime (0)
                utcTime: 2018-11-21 00:00:00 (UTC)
            notAfter: utcTime (0)
                utcTime: 2028-11-21 00:00:00 (UTC)
        subject: rdnSequence (0)
            rdnSequence: 3 items (id-at-commonName=GlobalSign RSA OV SSL CA 2018,id-at-organizationName=GlobalSign nv-sa,id-at-countryName=BE)
                RDNSequence item: 1 item (id-at-countryName=BE)
                RDNSequence item: 1 item (id-at-organizationName=GlobalSign nv-sa)
                RDNSequence item: 1 item (id-at-commonName=GlobalSign RSA OV SSL CA 2018)
        subjectPublicKeyInfo
            algorithm (rsaEncryption)
            subjectPublicKey: 3082010a0282010100a75ac9d50c18210023d5970febaedd5c686b6b8f5060137a81cb97…
        extensions: 7 items
            Extension (id-ce-keyUsage)
            Extension (id-ce-basicConstraints)
            Extension (id-ce-subjectKeyIdentifier)
            Extension (id-ce-authorityKeyIdentifier)
            Extension (id-pe-authorityInfoAccess)
            Extension (id-ce-cRLDistributionPoints)
            Extension (id-ce-certificatePolicies)
    algorithmIdentifier (sha256WithRSAEncryption)
    Padding: 0
    encrypted: 9990c82d5f428ad40b66db98037311d488865228538afbaddffd738e3a6704dbc3531470…

可以得到中级证书名为 GlobalSign RSA OV SSL CA 2018 ,证书组织为 GlobalSign nv-sa

其实这个就是根 CA 证书。在这个请求中没有找到,但在浏览器或者操作系统可以找到。一般的浏览器和系统都会内置该 CA 证书。所以根证书是受浏览器或者操作系统信任的,无需其他证书做担保。

如果想要自己的系统再信任某些非通用的权威机构的根 CA 证书,那么就去安装它。

比如我的 Windows 系统就安装了 GlobalSign 证书:

在这里插入图片描述

阶段四Server Key Exchange

密钥交换阶段,这个步骤是可选步骤,对 Certificate 阶段的补充,只有在这几个场景存在:

  • 协商采用了 RSA 加密,但是服务端证书没有提供 RSA 公钥。
  • 协商采用了 DH(EC Diffie-Hellman) 加密,但是服务端证书没有提供 DH 参数。
  • 协商采用 fortezza_kea 加密,但是服务端证书没有提供参数。

我们满足了哪一个场景?

可以知道我们前面协商了使用 EC Diffie-Hellman 算法,而且没带参数,所以这个包就是服务端带过来的用来协商 DH 密钥参数的。

TLSv1.2 Record Layer: Handshake Protocol: Server Key Exchange
    Content Type: Handshake (22)
    Version: TLS 1.2 (0x0303)
    Length: 333
    Handshake Protocol: Server Key Exchange
        Handshake Type: Server Key Exchange (12)
        Length: 329
        EC Diffie-Hellman Server Params
            Curve Type: named_curve (0x03)
            Named Curve: secp256r1 (0x0017)
            Pubkey Length: 65
            Pubkey: 04e820119cb27afedf33ae382d2d9322e402bd2fb5ff9a60854526557f2246d7e2c5fd0b…
            Signature Algorithm: rsa_pkcs1_sha512 (0x0601)
                Signature Hash Algorithm Hash: SHA512 (6)
                Signature Hash Algorithm Signature: RSA (1)
            Signature Length: 256
            Signature: 106dbf43752277fe9a311933e1dbb8a95ded603c0a9498823207273deb57d3cdbc751024…

这个包把 DH 算法需要的公钥给传递过来了,即 Pubkey: 04e820119cb27afedf33ae382d2d9322e402bd2fb5ff9a60854526557f2246d7e2c5fd0b…

同样这个包也携带了数字签名 Signature: 106dbf43752277fe9a311933e1dbb8a95ded603c0a9498823207273deb57d3cdbc751024… ,用服务端证书带过来的公钥验证一下完整性和来源。

阶段五Server Hello Done

通知客户端,版本和加密套件协商结束。

TLSv1.2 Record Layer: Handshake Protocol: Server Hello Done
    Content Type: Handshake (22)
    Version: TLS 1.2 (0x0303)
    Length: 4
    Handshake Protocol: Server Hello Done
        Handshake Type: Server Hello Done (14)
        Length: 0

这个 Server Hello Done,就像 TCP 协议的 ACK 确认包一样,这里服务端也给了个确认的信息,通知客户端已经做好进入下一个阶段的准备。

通过 Wireshark 抓包发现了一个现象,就是 Server Key Exchange 和 Server Hello Done 被放到了同一个 SSL 记录协议中,这是因为 SSL 记录协议具有组合功能。客户端收到这样的包后,会处理成两个单独的协议包,这又是 SSL 记录协议的分组功能。

Transport Layer Security
    TLSv1.2 Record Layer: Handshake Protocol: Server Key Exchange
    TLSv1.2 Record Layer: Handshake Protocol: Server Hello Done

这样做的好处,可以减少发 TCP 包的次数,减少 SSL 握手的时间。

如果在一些安全级别高的场景,服务端也会要求客户端上报证书,会有 Certificate Request 的 SSL 握手报文。这样的情况下,接下来会有完整的客户端证书上报服务端的流程。整个流程和阶段二三四五类似。

因为我们这里只需要验证服务端的证书,所以直接进入阶段六,开始最后的握手。这个阶段的主要目的,就是生产加密密钥,并进行安全传输。

阶段六Client Key Exchange

这里分为RSA加密和DHE加密

如果是RSA加密模式,客户端创建另一组随机数据,称为“预主机密”。客户端从服务器的 SSL 证书中获取公钥,加密预主机密,并将其发送到服务器;只有拥有私钥的人员才能解密预主机密。

服务器解密预主机密。请注意,这是唯一一次使用私钥!

现在,客户端和服务器都有客户端随机数、服务器随机数和预主机密。它们彼此独立,将这三个输入组合起来可得到会话密钥。它们都应达到相同的结果,并且会话期间的所有后续通信都用这些新密钥进行加密。

在这里插入图片描述

但是这个连接使用的是DHE加密模式,DHE 握手和 RSA 握手之间的主要区别在于如何生成预主机密。在 RSA 握手中,预主机密由客户端生成的随机数据组成。在 DHE 握手中,客户端和服务器使用商定的参数分别计算相同的预主机密。

服务器用其选择的密码套件、服务器随机数和 SSL 证书响应。在这里,DHE 握手开始与 RSA 握手有所不同:服务器还发送其 Diffie-Hellman (DH) 参数,该参数将用于计算预主机密。它还使用服务器的私钥加密客户端随机数、服务器随机数和 DH 参数。这是唯一一次使用私钥,它验证服务器是其声称的身份。

客户端使用公钥解密服务器的消息并验证 SSL 证书。然后,客户端使用其 DH 参数回复。

双方使用客户端的 DH 参数和服务器的 DH 参数,彼此分开计算预主机密。

然后,他们将此预主机密与客户端随机数和服务器随机数组合以获取会话密钥。

在这里插入图片描述

TLSv1.2 Record Layer: Handshake Protocol: Client Key Exchange
    Content Type: Handshake (22)
    Version: TLS 1.2 (0x0303)
    Length: 70
    Handshake Protocol: Client Key Exchange
        Handshake Type: Client Key Exchange (16)
        Length: 66
        EC Diffie-Hellman Client Params
            Pubkey Length: 65
            Pubkey: 04f226d9b00eb2dd0a9b22b3bd3e91e63198347e4b5f1d1ab80cf04430369a79945a9812…

这里,客户端不直接生成加密密钥,而是通过之前客户端和服务端生成的随机数又再生成一个随机数,使用前面协商好的用 EC Diffie-Hellman 算法进行加密传输给服务端。这个值又被称为 “premaster secret“。

在这个阶段过后,服务端和客户端都有三个随机数:客户端随机数、服务端随机数和预备主密钥。

在服务端收到了 Client Key Exchange 消息后,两端都按照相应的算法生成了主密钥,加密密钥交换完成。

交换完了,因为主密钥是两个端按照约定好的算法产生的,如何保证这个主密钥是正确的?

这时候会进入下一个阶段。客户端和服务端会对握手信息使用 SHA 做个摘要,用 AES 加密算法和主密钥加密,传递给对方验证。这种方式也称为消息认证。就是下面的过程:

阶段七

Change Cipher Spec(Client)

客户端通知服务端,后续的报文将会被加密。

Encrypted Handshake Message(Client)

这里就是客户端的 Client Finished 消息。

也是整个 SSL 过程中,发送给服务端的第一个加密消息。

服务端接收后,服务端用同样的方式计算出已交互的握手消息的摘要,与用主密钥解密后的消息进行对比,一致的话,说明两端生成的主密钥一致,完成了密钥交换。

Change Cipher Spec(Server)

服务端通知客户端,后续的报文将会被加密。

Encrypted Handshake Message(Server)

这里就是服务端的 Server Finish 消息。

和上面的客户端的 Encrypted Handshake Message 一样,是服务端发出的第一条加密信息。

客户端按照协商好的主密钥解密并验证正确后,SSL 握手阶段完成。

3、TLS/SSL数据传输

SSL 握手成功,已经可以对接下来的数据加密了,接下来各种应用层协议都可以加密传输。

Application Data
Transport Layer Security
    TLSv1.2 Record Layer: Application Data Protocol: http-over-tls
        Content Type: Application Data (23)
        Version: TLS 1.2 (0x0303)
        Length: 830
        Encrypted Application Data:00000000000000015832eeb57b5248a84dc12befee545c4cf03b0379a6459306ea10900b…
        [Application Data Protocol: http-over-tls]

从这里,不知道密钥是无法知道这里传输的是什么数据,连传输的是什么协议的内容都不知道。

所以 SSL 协议是很独立的,这里是对 HTTP 进行了加密,也可以对其他协议进行加密。它就像是 TCP 和应用层协议的中间层,为上层协议提供了加密的数据传输。

Encryted Alert

SSL 警告消息,因为是加密的内容,所以单从 Wireshark 看不出警报的内容。

Transport Layer Security
    TLSv1.2 Record Layer: Encrypted Alert
        Content Type: Alert (21)
        Version: TLS 1.2 (0x0303)
        Length: 26
        Alert Message: Encrypted Alert

但因为警报消息经常只是客户端用来提示服务端 SSL 传输结束,对照抓包到的内容确实如此。所以这里只是 SSL 传输结束的一个信号。

发出了 Encryted Alert 后客户端数据传输完毕,准备进入四次挥手断开 TCP 连接。

4、四次挥手

客户端发送完 HTTP 的数据,也正确地获取到服务端的响应。完成了 HTTPS 的请求工作后,接下来要关闭 TCP 连接。关闭 TCP 连接一共分成四步,也可以成为四次挥手。对应包如下:

在这里插入图片描述

第一次挥手:

客户端 A 发出 FIN + ACK 包,通知服务端数据传输结束,可以关闭连接了。同样这是一个 ACK 确认包,指出希望的下一个包的序号为 11596。这个阶段后,客户端进入 FIN_WAIT_1 状态,不再发送数据给服务端,但是如果服务端还在传数据过来,客户端的 ACK 确认报文还会有。

服务端进入 CLOSE_WAIT 状态。这个状态再发出 ACK 确认包后解除。

第二次挥手:

接收端 B 收到第一次挥手的包后,会先给一个 ACK 确认包,为第二次挥手。

这里有个疑问,既然收到了 A 的结束信息,为什么不马上结束呢?因为 A 完成数据传输,但是 B 可能还有数据没有传完,所以比三次握手会多一个步骤。

收到客户端的 FIN 包后,已经知道客户端结束数据传输了,所以服务端后面数据传输结束,就可以直接通知客户端可以结束了。

客户端收到 ACK 确认包后,进入 FIN_WAIT_2 状态。

第三次挥手:

如果服务端数据还没有传完,会继续传给客户端。

等服务端的数据完全传完后,会再发一个 FIN + ACK 包,通知客户端数据传完了。

这个阶段后,服务端进入 LAST_ACK 状态,即等待客户端最后一个 ACK 报文。

第四次挥手:

发送端 A 收到 B 发出的 FIN + ACK 后,进入 TIME_WAIT 状态。服务端收到 FIN 后进入 CLOSED 状态关闭连接。

经过 2MSL 时间,没有问题后会关闭连接。也进入 CLOSED 状态。

为什么有个 TIME_WAIT ?

原因是有可能服务端一直没有收到 FIN + ACK,有可能触发超时重传,又发了一个 FIN 给客户端,客户端要重新发送最后一个包。

在正常情况下,TCP连接的关闭需要连接的两端进行四次分组交换,具体过程是:执行主动关闭的一端(A端)会首先发送FIN包给对端(B端),B端收到FIN包后会发送一个ACK包给A段;B段执行关闭操作,发送FIN给A端,A端发送一个ACK给B端,连接彻底关闭。分组交换和状态迁移如下图所示:

在这里插入图片描述

这里还有一个问题,我们看到捕获到的数据包并不是上边的顺序,而是下边的顺序,

在这里插入图片描述

这是因为 通常情况下,只有执行主动关闭的一端会进入TIME_WAIT状态,还有一种情况会导致连接的两端都进入TIME_WAIT状态。当TCP的两端同时给对端发送FIN包,两端的TCP状态均从ESTABLISHED变为FIN_WAIT_1,在FIN_WAIT_1状态下接收到FIN包后,状态会由FIN_WAIT_1迁移到CLOSING,并发送最后的ACK。收到最后的ACK后,状态变迁为TIME_WAIT状态,如上图所示:

上述的两种情况只是TCP连接关闭情况的一部分,在其他情况下,内核可能给对端发送的不是FIN包,而是RST包。在这种情况下,有可能是应用层序的问题,也可能是内核资源短缺造成的,如何来查找和确定这些异常情况的原因,需要对内核的实现有一个比较全面的了解。

例如使用rst包
在这里插入图片描述

就不在这里进行探讨了。

5、总结

整个 SSL 握手主要是要完成这几个目标:

  • 对服务端身份的验证,或者客户端
  • 协商好对称加密算法和对称加密密钥

6、扩展

keyless SSL

Keyless SSL 是使用云供应商进行 SSL 加密的公司提供的一项服务。通常,这意味着云供应商必须知道公司的私钥,但 Keyless SSL 是规避该问题的一种方法。出于监管原因,许多组织无法共享其私钥。通过 Keyless SSL,这些组织仍然能够使用 TLS 并利用云,同时保证密钥的安全。

在这里插入图片描述

Keyless SSL 基于以下事实:在 TLS 握手期间仅使用私钥一次,即 TLS 通信会话开始时发生。Keyless SSL 通过拆分 TLS 握手的步骤来实现。提供 Keyless SSL 的云供应商将该过程的私钥部分迁移到另一台服务器,通常是客户保留在本地的服务器。

握手期间需要使用私钥来解密或加密数据时,供应商的服务器会将所需数据转发到客户的私钥服务器。私钥对客户服务器上的数据进行加密或解密,然后将数据发回供应商的服务器,而 TLS 握手如常继续。

Keyless SSL 仅从云供应商的角度来看为“无密钥”:他们从不查看其客户的私钥,但客户仍拥有并可使用私钥。同时,公钥仍如常在客户端使用。

在这种情况下,上面的SSL握手阶段的流程变为如下的:

在这里插入图片描述
在这里插入图片描述

前向保密

短暂 Diffie-Hellman 握手尽管比 RSA 握手花费的时间稍长,但有称为前向保密的优点。由于私钥仅用于身份验证,因此攻击者无法使用它来查找任何指定的会话密钥。

前向保密确保加密的数据即使私钥公开时仍保持加密。这也称为“完美前向保密”。如果每个通信会话使用唯一的会话密钥,并且会话密钥与私钥分开生成,则前向保密是可能的。如果单个会话密钥受损,则攻击者只能解密该会话;所有其他会话都将保持加密状态。

在为前向保密设置的协议中,私钥在初始握手过程中用于身份验证,但是不用于加密。短暂 Diffie-Hellman 握手将会话密钥与私钥分开生成,因此具有前向保密。

相反,RSA 没有前向保密;在私钥受损的情况下,攻击者可以解密过去对话的会话密钥,因为他们可以解密明文形式的预主机密以及客户端随机数和服务器随机数。通过将这三者结合起来,攻击者可以得到任何指定的会话密钥。

Session ID 和 Session Ticket

SSL 中的 session 会跟 HTTP 的 session 类似,都是用来保存客户端和服务端之间交互的一些记录。这里的 SSL 的 session 保存的是 SSL 的握手记录。使用 session

无论用哪种方式,客户端都会记录 Session编号,只是 Session Ticket 相比 Session ID,客户端还多该次会话使用的加密信息,会在 SSL 握手过程中由客户端直接发给服务端。所以两者的主要区别,就是加密的握手记录谁负责保存的问题。Session ID 是服务端保存握手记录,Session Ticket 是 客户端保存握手记录。

SSL 握手过程,因为多了几次 RTT,还有加解密的计算,一直以来都是 HTTPS 的耗时大户,是要被优化的重点。复用了握手记录,可以很显著地提高 HTTPS 的效率。

什么是 SNI?

SNI(Server Name Indication)是 TLS 的扩展,用来解决一个服务器拥有多个域名的情况。

在 TLS 握手信息中只携带了客户端要访问的目标地址,并没有指定和哪个域名的虚拟机通信。这样会导致一个问题,如果一台服务器有多个虚拟主机,且每个主机的域名不一样,使用了不一样的证书,该和哪台虚拟主机进行通信?

和 HTTP 协议用来解决服务器多域名的方案类似,HTTP 在请求头中使用 Host 字段来指定要访问的域名。TLS 的做法,也是加 Host,在 TLS 握手第一阶段 ClientHello 的报文中添加。

SNI 在 TLSv1.2 开始得到支持。从 OpenSSL 0.9.8 版本开始支持。所以基本市场上的终端设备都支持。

Server Name Indication extension
    Server Name list length: 36
    Server Name Type: host_name (0)
    Server Name length: 33
    Server Name: datacollector-drcn.dt.hicloud.com

这里指定了该 TLS 握手的目标域名为 datacollector-drcn.dt.hicloud.com

通过 SNI,拥有多虚拟机主机和多域名的服务器就可以正常建立 TLS 连接了。

什么是ALPN?

ALPN 全称 Application Layer Protocol Negotiation。顾名思义,这个就是用来协商应用层协议的。ALPN 是 TLS 的扩展,在 TLS 握手过程中加入 ALPN 扩展实现。

协商的过程,发生在 TLS 握手过程中:

  • Client Hello,客户端会发一个自己支持的 HTTP 协议列表。
  • Server Hello,服务端选择能够使用的协议,返回给客户端

在这里插入图片描述

服务端如果支持 http2,双方都可以使用 http2 来进行通信了。所以这个的设计一个目的就是用来推进 http2 的使用的。

# client hello
Extension: application_layer_protocol_negotiation (len=14)
    Type: application_layer_protocol_negotiation (16)
    Length: 14
    ALPN Extension Length: 12
    ALPN Protocol
        ALPN string length: 2
        ALPN Next Protocol: h2
        ALPN string length: 8
        ALPN Next Protocol: http/1.1
        
# server hello
Extension: application_layer_protocol_negotiation (len=11)
    Type: application_layer_protocol_negotiation (16)
    Length: 11
    ALPN Extension Length: 9
    ALPN Protocol
        ALPN string length: 8
        ALPN Next Protocol: http/1.1

在 Client Hello 包中,ALPN 扩展字段携带着 h2 和 http/1.1 协议列表。返回的 Server Hello 包的 ALPN 扩展字段中,我们看到了 http/1.1。

参考自:
https://blog.csdn.net/firefile/article/details/80537053、
https://zhuanlan.zhihu.com/p/150403658、
计算机网络(谢希仁)、
https://www.cloudflare.com/zh-cn/learning/ssl/keyless-ssl/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值