结合实例学习TLS1.2协议

失去的东西,其实从来未曾真正地属于你,也不必惋惜,始终真心真意

Posted by yishuifengxiao on 2026-07-30

完整流程

ClientHello                  -------->
ServerHello
Certificate*
ServerKeyExchange*
CertificateRequest*
<-------- ServerHelloDone
Certificate*
ClientKeyExchange
CertificateVerify*
[ChangeCipherSpec]
Finished -------->
[ChangeCipherSpec]
<-------- Finished
Application Data <-------> Application Data

据结构定义

Record Layer

struct {
uint8 major;
uint8 minor;
} ProtocolVersion;

ProtocolVersion version = { 3, 3 }; /* TLS v1.2*/

enum {
change_cipher_spec(20), alert(21), handshake(22),
application_data(23), (255)
} ContentType;

struct {
ContentType type;
ProtocolVersion version;
uint16 length;
opaque fragment[TLSPlaintext.length];
} TLSPlaintext;

struct {
ContentType type;
ProtocolVersion version;
uint16 length;
opaque fragment[TLSCompressed.length];
} TLSCompressed;

struct {
ContentType type;
ProtocolVersion version;
uint16 length;
select (SecurityParameters.cipher_type) {
case stream: GenericStreamCipher;
case block: GenericBlockCipher;
case aead: GenericAEADCipher;
} fragment;
} TLSCiphertext;

stream-ciphered struct {
opaque content[TLSCompressed.length];
opaque MAC[SecurityParameters.mac_length];
} GenericStreamCipher;
struct {
opaque IV[SecurityParameters.record_iv_length];
block-ciphered struct {
opaque content[TLSCompressed.length];
opaque MAC[SecurityParameters.mac_length];
uint8 padding[GenericBlockCipher.padding_length];
uint8 padding_length;
};
} GenericBlockCipher;

struct {
opaque nonce_explicit[SecurityParameters.record_iv_length];
aead-ciphered struct {
opaque content[TLSCompressed.length];
};
} GenericAEADCipher;

Change Cipher Specs Message

struct {
enum { change_cipher_spec(1), (255) } type;
} ChangeCipherSpec;

Handshake Protocol

enum {
hello_request(0), client_hello(1), server_hello(2),
certificate(11), server_key_exchange (12),
certificate_request(13), server_hello_done(14),
certificate_verify(15), client_key_exchange(16),
finished(20)
(255)
} HandshakeType;

struct {
HandshakeType msg_type;
uint24 length;
select (HandshakeType) {
case hello_request: HelloRequest;
case client_hello: ClientHello;
case server_hello: ServerHello;
case certificate: Certificate;
case server_key_exchange: ServerKeyExchange;
case certificate_request: CertificateRequest;
case server_hello_done: ServerHelloDone;
case certificate_verify: CertificateVerify;
case client_key_exchange: ClientKeyExchange;
case finished: Finished;
} body;
} Handshake;

Hello Messages

struct { } HelloRequest;

struct {
uint32 gmt_unix_time;
opaque random_bytes[28];
} Random;

opaque SessionID<0..32>;

uint8 CipherSuite[2];

enum { null(0), (255) } CompressionMethod;

struct {
ProtocolVersion client_version;
Random random;
SessionID session_id;
CipherSuite cipher_suites<2..2^16-2>;
CompressionMethod compression_methods<1..2^8-1>;
select (extensions_present) {
case false:
struct {};
case true:
Extension extensions<0..2^16-1>;
};
} ClientHello;

struct {
ProtocolVersion server_version;
Random random;
SessionID session_id;
CipherSuite cipher_suite;
CompressionMethod compression_method;
select (extensions_present) {
case false:
struct {};
case true:
Extension extensions<0..2^16-1>;
};
} ServerHello;

struct {
ExtensionType extension_type;
opaque extension_data<0..2^16-1>;
} Extension;
enum {
signature_algorithms(13), (65535)
} ExtensionType;

enum{
none(0), md5(1), sha1(2), sha224(3), sha256(4), sha384(5),
sha512(6), (255)
} HashAlgorithm;
enum {
anonymous(0), rsa(1), dsa(2), ecdsa(3), (255)
} SignatureAlgorithm;

struct {
HashAlgorithm hash;
SignatureAlgorithm signature;
} SignatureAndHashAlgorithm;

SignatureAndHashAlgorithm
supported_signature_algorithms<2..2^16-1>;

Server Authentication and Key Exchange Messages

opaque ASN.1Cert<2^24-1>;

struct {
ASN.1Cert certificate_list<0..2^24-1>;
} Certificate;

enum { dhe_dss, dhe_rsa, dh_anon, rsa,dh_dss, dh_rsa
/* may be extended, e.g., for ECDH -- see [TLSECC] */
} KeyExchangeAlgorithm;

struct {
opaque dh_p<1..2^16-1>;
opaque dh_g<1..2^16-1>;
opaque dh_Ys<1..2^16-1>;
} ServerDHParams; /* Ephemeral DH parameters */
struct {
select (KeyExchangeAlgorithm) {
case dh_anon:
ServerDHParams params;
case dhe_dss:
case dhe_rsa:
ServerDHParams params;
digitally-signed struct {
opaque client_random[32];
opaque server_random[32];
ServerDHParams params;
} signed_params;
case rsa:
case dh_dss:
case dh_rsa:
struct {} ;
/* message is omitted for rsa, dh_dss, and dh_rsa */
/* may be extended, e.g., for ECDH -- see [TLSECC] */
} ServerKeyExchange;

enum {
rsa_sign(1), dss_sign(2), rsa_fixed_dh(3), dss_fixed_dh(4),
rsa_ephemeral_dh_RESERVED(5), dss_ephemeral_dh_RESERVED(6),
fortezza_dms_RESERVED(20),
(255)
} ClientCertificateType;

opaque DistinguishedName<1..2^16-1>;

struct {
ClientCertificateType certificate_types<1..2^8-1>;
DistinguishedName certificate_authorities<0..2^16-1>;
} CertificateRequest;

struct { } ServerHelloDone;

Client Authentication and Key Exchange Messages

struct {
select (KeyExchangeAlgorithm) {
case rsa:
EncryptedPreMasterSecret;
case dhe_dss:
case dhe_rsa:
case dh_dss:
case dh_rsa:
case dh_anon:
ClientDiffieHellmanPublic;
} exchange_keys;
} ClientKeyExchange;

struct {
ProtocolVersion client_version;
opaque random[46];
} PreMasterSecret;

struct {
public-key-encrypted PreMasterSecret pre_master_secret;
} EncryptedPreMasterSecret;

enum { implicit, explicit } PublicValueEncoding;

struct {
select (PublicValueEncoding) {
case implicit: struct {};
case explicit: opaque DH_Yc<1..2^16-1>;
} dh_public;
} ClientDiffieHellmanPublic;

struct {
digitally-signed struct {
opaque handshake_messages[handshake_messages_length];
}
} CertificateVerify;

Handshake Finalization Message

struct {
opaque verify_data[verify_data_length];
} Finished;

The Cipher Suite

以下值定义了ClientHello和ServerHello消息中使用的密码套件代码。

密码套件定义了TLS 1.2版本中支持的密码规范。

TLS_NULL_WITH_ULL_NULL已指定,是该通道上第一次握手期间TLS连接的初始状态,但不得协商,因为它提供的保护不比不安全的连接多。

CipherSuite TLS_NULL_WITH_NULL_NULL               = { 0x00,0x00 };

以下CipherSuite定义要求服务器提供可用于密钥交换的RSA证书。服务器可以在证书请求消息中请求任何具有签名能力的证书。

CipherSuite TLS_RSA_WITH_NULL_MD5                 = { 0x00,0x01 };
CipherSuite TLS_RSA_WITH_NULL_SHA = { 0x00,0x02 };
CipherSuite TLS_RSA_WITH_NULL_SHA256 = { 0x00,0x3B };
CipherSuite TLS_RSA_WITH_RC4_128_MD5 = { 0x00,0x04 };
CipherSuite TLS_RSA_WITH_RC4_128_SHA = { 0x00,0x05 };
CipherSuite TLS_RSA_WITH_3DES_EDE_CBC_SHA = { 0x00,0x0A };
CipherSuite TLS_RSA_WITH_AES_128_CBC_SHA = { 0x00,0x2F };
CipherSuite TLS_RSA_WITH_AES_256_CBC_SHA = { 0x00,0x35 };
CipherSuite TLS_RSA_WITH_AES_128_CBC_SHA256 = { 0x00,0x3C };
CipherSuite TLS_RSA_WITH_AES_256_CBC_SHA256 = { 0x00,0x3D };

以下密码套件定义用于服务器身份验证(以及可选的客户端身份验证)Diffie-Hellman。DH表示密码套件,其中服务器的证书包含由证书颁发机构(CA)签名的Diffie-Hellman参数。DHE表示临时Diffie-Hellman,其中Diffie-Hell参数由CA签名的可签名证书签名。服务器使用的签名算法在CipherSuite名称的DHE组件后指定。服务器可以向客户端请求任何支持签名的证书进行客户端身份验证,也可以请求Diffie-Hellman证书。客户端提供的任何Diffie-Hellman证书都必须使用服务器描述的参数(组和生成器)。

CipherSuite TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA      = { 0x00,0x0D };
CipherSuite TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA = { 0x00,0x10 };
CipherSuite TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA = { 0x00,0x13 };
CipherSuite TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA = { 0x00,0x16 };
CipherSuite TLS_DH_DSS_WITH_AES_128_CBC_SHA = { 0x00,0x30 };
CipherSuite TLS_DH_RSA_WITH_AES_128_CBC_SHA = { 0x00,0x31 };
CipherSuite TLS_DHE_DSS_WITH_AES_128_CBC_SHA = { 0x00,0x32 };
CipherSuite TLS_DHE_RSA_WITH_AES_128_CBC_SHA = { 0x00,0x33 };
CipherSuite TLS_DH_DSS_WITH_AES_256_CBC_SHA = { 0x00,0x36 };
CipherSuite TLS_DH_RSA_WITH_AES_256_CBC_SHA = { 0x00,0x37 };
CipherSuite TLS_DHE_DSS_WITH_AES_256_CBC_SHA = { 0x00,0x38 };
CipherSuite TLS_DHE_RSA_WITH_AES_256_CBC_SHA = { 0x00,0x39 };
CipherSuite TLS_DH_DSS_WITH_AES_128_CBC_SHA256 = { 0x00,0x3E };
CipherSuite TLS_DH_RSA_WITH_AES_128_CBC_SHA256 = { 0x00,0x3F };
CipherSuite TLS_DHE_DSS_WITH_AES_128_CBC_SHA256 = { 0x00,0x40 };
CipherSuite TLS_DHE_RSA_WITH_AES_128_CBC_SHA256 = { 0x00,0x67 };
CipherSuite TLS_DH_DSS_WITH_AES_256_CBC_SHA256 = { 0x00,0x68 };
CipherSuite TLS_DH_RSA_WITH_AES_256_CBC_SHA256 = { 0x00,0x69 };
CipherSuite TLS_DHE_DSS_WITH_AES_256_CBC_SHA256 = { 0x00,0x6A };
CipherSuite TLS_DHE_RSA_WITH_AES_256_CBC_SHA256 = { 0x00,0x6B };

以下密码套件用于完全匿名的Diffie-Hellman通信,其中任何一方都没有经过身份验证。请注意,此模式容易受到中间人攻击。因此,使用此模式的用途有限:除非应用层特别要求允许匿名密钥交换,否则TLS 1.2实现不得使用这些密码套件。(匿名密钥交换有时是可以接受的,例如,当没有设置身份验证时,或者当TLS用作具有其他确保身份验证手段的更复杂安全协议的一部分时,支持机会加密。)

CipherSuite TLS_DH_anon_WITH_RC4_128_MD5          = { 0x00,0x18 };
CipherSuite TLS_DH_anon_WITH_3DES_EDE_CBC_SHA = { 0x00,0x1B };
CipherSuite TLS_DH_anon_WITH_AES_128_CBC_SHA = { 0x00,0x34 };
CipherSuite TLS_DH_anon_WITH_AES_256_CBC_SHA = { 0x00,0x3A };
CipherSuite TLS_DH_anon_WITH_AES_128_CBC_SHA256 = { 0x00,0x6C };
CipherSuite TLS_DH_anon_WITH_AES_256_CBC_SHA256 = { 0x00,0x6D };

请注意,使用非匿名密钥交换而不实际验证密钥交换本质上等同于匿名密钥交换,并且适用相同的预防措施。虽然非匿名密钥交换通常比匿名密钥交换涉及更高的计算和通信成本,但当应用层允许匿名密钥交换时,不禁用非匿名密钥交易所可能有利于互操作性。

注意:为避免与SSL 3中基于Fortezza的密码套件发生冲突,保留了密码套件值{0x00,0x1C}和{ 0x00, 0x1D }

Client Hello

当客户端首次连接到服务器时,需要将ClientHello作为其第一条消息发送。客户端还可以响应HelloRequest或主动发送ClientHello,以便重新协商现有连接中的安全参数。

数据结构为:
ClientHello的定义

struct {
ProtocolVersion client_version;
Random random;
SessionID session_id;
CipherSuite cipher_suites<2..2^16-2>;
CompressionMethod compression_methods<1..2^8-1>;
select (extensions_present) {
case false:
struct {};
case true:
Extension extensions<0..2^16-1>;
};
} ClientHello;

struct {
uint8 major;
uint8 minor;
} ProtocolVersion;

struct {
uint32 gmt_unix_time;
opaque random_bytes[28];
} Random;

opaque SessionID<0..32>;

uint8 CipherSuite[2]; /* Cryptographic suite selector */

enum { null(0), (255) } CompressionMethod;

Handshake的定义

enum {
hello_request(0), client_hello(1), server_hello(2),
certificate(11), server_key_exchange (12),
certificate_request(13), server_hello_done(14),
certificate_verify(15), client_key_exchange(16),
finished(20), (255)
} HandshakeType;

struct {
HandshakeType msg_type; /* handshake type */
uint24 length; /* bytes in message */
select (HandshakeType) {
case hello_request: HelloRequest;
case client_hello: ClientHello;
case server_hello: ServerHello;
case certificate: Certificate;
case server_key_exchange: ServerKeyExchange;
case certificate_request: CertificateRequest;
case server_hello_done: ServerHelloDone;
case certificate_verify: CertificateVerify;
case client_key_exchange: ClientKeyExchange;
case finished: Finished;
} body;
} Handshake;

记录层

struct {
uint8 major;
uint8 minor;
} ProtocolVersion;

enum {
change_cipher_spec(20), alert(21), handshake(22),
application_data(23), (255)
} ContentType;

struct {
ContentType type;
ProtocolVersion version;
uint16 length;
opaque fragment[TLSPlaintext.length];
} TLSPlaintext;

数据示例

1603030075010000710303FC7C6231693E7A746B92D387AABBD24E3DBB45117B309E9B89A2FD4902122CD9000002C02B010000460000001900170000147273702E6573696D2E776874792E636F6D2E636E000D0012001004030503060302030401050106010201000A000400020017000B00020100FF01000100

进行拆分

16 -- record.type # 内容类型:Handshake = 22 (0x16)
0303 -- record.version 协议版本:TLS 1.2
0075 -- record.length 记录长度
01 -- Handshake.msg_type # client_hello(1) 握手消息类型:
000071 -- Handshake.length 消息长度(3字节大端序)
0303 -- ClientHello.client_version 协议版本:TLS 1.2 (0x0303)
FC7C6231 693E7A746B92D387AABBD24E3DBB45117B309E9B89A2FD4902122CD9 -- ClientHello.random 客户端随机数(32字节)
00 -- ClientHello.session_id 会话ID长度:0(新会话,不恢复)
0002 C02B -- ClientHello.cipher_suites密码套件列表长度
01 00 --ClientHello.compression_methods 压缩方法列表:仅支持 null 压缩(不压缩)
0046 -- ClientHello.extensions组装扩展列表:总长度(2字节)+ 所有扩展
0000 0019 -- 扩展类型:SNI (0x0000) 扩展长度
0017 -- SNI 扩展数据长度
00 -- 服务器名称类型:0x00 表示主机名
0014 -- 主机名长度(大端序)
7273702E6573696D2E776874792E636F6D2E636E --主机名数据
000D -- 扩展类型:signature_algorithms (0x000D)
0012 -- 扩展长度
0010 -- 算法列表长度
04030503060302030401050106010201 -- 算法列表
000A -- 扩展类型:supported_groups (0x000A)
0004 -- 扩展长度
0002 -- 长度=2,
0017 -- 支持的组列表
000B -- 扩展类型:ec_point_formats (0x000B)
0002 -- 扩展长度
01 -- 长度=1
00 --0x00 = 压缩格式, 0x01 = 未压缩格式
FF01 -- 扩展类型:renegotiation_info (0xFF01)
0001 -- 扩展长度=1
00 --数据=0x00(不支持安全重协商)

SNI

SNI的定义如下:

struct {
NameType name_type;
select (name_type) {
case host_name: HostName;
} name;
} ServerName;

enum {
host_name(0), (255)
} NameType;

opaque HostName<1..2^16-1>;

struct {
ServerName server_name_list<1..2^16-1>
} ServerNameList;

对应的数据项

        0000 0019 -- 扩展类型:SNI (0x0000) 扩展长度
                0017 -- SNI 扩展数据长度
                    00 -- 服务器名称类型:0x00 表示主机名
                        0014 -- 主机名长度(大端序)
                        7273702E6573696D2E776874792E636F6D2E636E --主机名数据

整体结构:TLS 扩展块

一个 TLS 扩展的基本结构是:

Extension {
ExtensionType extension_type; // 2 字节
opaque extension_data<0..2^16-1>; // 2 字节长度 + 数据
}

数据开头是:

00 00   -- extension_type = 0x0000 (server_name)
00 19 -- extension_data 的长度 = 0x0019 = 25 字节

所以剩下的 25 字节 就是 extension_data,即 ServerNameList。


extension_data即 ServerNameList

RFC 6066 中 ServerNameList 定义为:

struct {
ServerName server_name_list<1..2^16-1>;
} ServerNameList;

也就是说,server_name_list 是一个 变长向量,编码为 2 字节长度 + 元素

从数据中,紧接着是:

00 17   -- server_name_list 的长度 = 0x0017 = 23 字节

这 23 字节就是 server_name_list 的实际内容(即一个或多个 ServerName 结构)。


ServerName 结构

ServerName 定义为:

struct {
NameType name_type;
select (name_type) {
case host_name: HostName;
} name;
} ServerName;

enum { host_name(0), (255) } NameType;
opaque HostName<1..2^16-1>;

内容:

  • name_type:1 字节,值为 0x00(表示主机名)
  • name:根据 name_type 选择 HostName 类型。HostName 也是一个变长向量,编码为 2 字节长度 + 主机名字符串

数据在 00 17 之后是:

00        -- name_type = 0x00
00 14 -- HostName 的长度 = 0x0014 = 20 字节
72 73 70 2E 65 73 69 6D 2E 77 68 74 79 2E 63 6F 6D 2E 63 6E -- ASCII "rsp.esim.whty.com.cn"

signature_algorithms

结构定义为

enum {
none(0), md5(1), sha1(2), sha224(3), sha256(4), sha384(5),
sha512(6), (255)
} HashAlgorithm;

enum { anonymous(0), rsa(1), dsa(2), ecdsa(3), (255) } SignatureAlgorithm;

struct {
HashAlgorithm hash;
SignatureAlgorithm signature;
} SignatureAndHashAlgorithm;

SignatureAndHashAlgorithm
supported_signature_algorithms<2..2^16-2>;

对应的数据

            000D -- 扩展类型:signature_algorithms (0x000D)
                0012 -- 扩展长度
                    0010 -- 算法列表长度
                        0403
                        0503
                        0603
                        0203
                        0401
                        0501
                        0601
                        0201 -- 算法列表

提供的这段数据是 TLS ClientHello 中 signature_algorithms(签名算法)扩展 的完美编码实例,它完全遵循 RFC 5246 第 7.4.1.4.1 节的定义。下面我们逐层拆解,看它如何与数据结构对应。


整体结构:TLS 扩展块

每个 TLS 扩展的通用结构是:

Extension {
ExtensionType extension_type; // 2 字节
opaque extension_data<0..2^16-1>; // 2 字节长度 + 数据
}

数据开头是:

  • 00 0Dextension_type = 0x000D,即 signature_algorithms 扩展。
  • 00 12extension_data 长度 = 0x0012 = 18 字节

因此,接下来的 18 字节 就是 extension_data


extension_data 的结构

RFC 5246 定义 extension_data 的内容为:

SignatureAndHashAlgorithm
supported_signature_algorithms<2..2^16-2>;

也就是说,supported_signature_algorithms 是一个变长向量,编码为 2 字节长度 + 元素列表

紧接着的 18 字节是:

  • 00 10 → 列表长度 = 0x0010 = 16 字节
  • 剩余的 16 字节 是算法对列表。

算法列表(16 字节 = 8 对)

每个算法对(SignatureAndHashAlgorithm)占 2 字节:

struct {
HashAlgorithm hash; // 1 字节
SignatureAlgorithm signature; // 1 字节
} SignatureAndHashAlgorithm;

现在拆解剩余的 16 字节:

字节对 哈希值 (Hash) 哈希名称 签名值 (Signature) 签名名称 含义
04 03 0x04 SHA‑256 0x03 ECDSA SHA256 + ECDSA
05 03 0x05 SHA‑384 0x03 ECDSA SHA384 + ECDSA
06 03 0x06 SHA‑512 0x03 ECDSA SHA512 + ECDSA
02 03 0x02 SHA‑1 0x03 ECDSA SHA1 + ECDSA
04 01 0x04 SHA‑256 0x01 RSA SHA256 + RSA
05 01 0x05 SHA‑384 0x01 RSA SHA384 + RSA
06 01 0x06 SHA‑512 0x01 RSA SHA512 + RSA
02 01 0x02 SHA‑1 0x01 RSA SHA1 + RSA

注意0x04 是 SHA256,0x05 是 SHA384,0x06 是 SHA512,0x02 是 SHA1。0x03 是 ECDSA,0x01 是 RSA。


总结为什么是这种格式

  1. 嵌套长度:每层数据都有长度前缀(00 12 是扩展总长,00 10 是列表总长),确保解析器能准确知道数据边界。
  2. 固定顺序:先放扩展类型,再放扩展长度,再放数据。数据内部先放列表长度,再放列表元素。
  3. 固定元素大小:每个算法对固定 2 字节(1 字节哈希 + 1 字节签名),方便快速遍历。
  4. 大端序:所有长度字段均使用大端(网络字节序)。

这种格式可以让服务器精确知道客户端支持哪些 (哈希, 签名) 组合,从而在 ServerKeyExchange 中选择一个双方都支持的算法进行签名验证。

supported_groups

结构定义

struct {
NamedGroup named_group_list<2..2^16-1>;
} NamedGroupList;

enum {
secp256r1(0x0017), secp384r1(0x0018), secp521r1(0x0019),
x25519(0x001D), x448(0x001E),
// ...
} NamedGroup;

对应的数据

000A -- 扩展类型:supported_groups (0x000A)
0004 -- 扩展长度
0002 -- 长度=2,
0017 -- 支持的组列表

提供的这段数据是 TLS ClientHello 中 supported_groups(支持的组)扩展 的一个典型编码实例。它告诉服务器客户端支持哪些椭圆曲线(或 Diffie‑Hellman 组)用于密钥交换。

现在对照 RFC 7919(或 RFC 4492) 定义的结构,逐层拆解这段数据。


整体扩展块结构

TLS 扩展的通用定义如下(RFC 5246 第 7.4.1.4 节):

struct {
ExtensionType extension_type; // 2 字节
opaque extension_data<0..2^16-1>; // 2 字节长度 + 数据
} Extension;

给出的数据开头是:

  • 00 0Aextension_type = 0x000A,即 supported_groups(旧称 elliptic_curves)。
  • 00 04extension_data 的长度 = 0x0004 = 4 字节

因此,接下来的 4 字节 就是 extension_data 的内容。


extension_data 的结构

RFC 7919 第 4 节定义了 supported_groups 的扩展数据:

struct {
NamedGroup named_group_list<2..2^16-1>;
} NamedGroupList;

enum {
secp256r1(0x0017), secp384r1(0x0018), secp521r1(0x0019),
x25519(0x001D), x448(0x001E),
// ...
} NamedGroup;

NamedGroupList 是一个变长向量,编码为:

  • 前 2 字节:列表的总长度(以字节为单位)
  • 紧接着是 NamedGroup 元素列表(每个 2 字节)。

    extension_data 接下来的 4 字节是:

    00 02   -- named_group_list 的长度 = 0x0002(表示后续有 2 字节数据)
    00 17 -- 第一个(也是唯一一个)NamedGroup 值 = 0x0017(即 secp256r1)

为什么长度是 00 02 而不是直接写组数量

因为 named_group_list 是一个字节向量,它的长度表示的是总字节数,而不是元素个数。一个 NamedGroup 占 2 字节,所以列表长度为 2 字节时,正好容纳一个组。如果列表包含两个组(例如 P-256 和 X25519),长度就是 00 04,后面跟着 00 17 00 1D


为什么组值 00 17 对应 secp256r1

IANA 为每个命名组分配了固定的 16 位编号。0x0017 代表 NIST P‑256 曲线(secp256r1),这是 TLS 中最常用的曲线之一。其他常见值:

  • 0x0018 → secp384r1
  • 0x001D → X25519
  • 0x001E → X448

ec_point_formats

它的结构严格遵循 RFC 4492(ECC 密码套件)第 5.1.2 节的定义。

对应的数据

000B -- 扩展类型:ec_point_formats (0x000B)
0002 -- 扩展长度
01 -- 长度=1
00 --0x00 = 未压缩格式, 0x01 = 压缩格式

整体扩展块结构

TLS 扩展的通用格式(RFC 5246 第 7.4.1.4 节):

struct {
ExtensionType extension_type; // 2 字节
opaque extension_data<0..2^16-1>; // 2 字节长度 + 数据
} Extension;

数据开头是:

  • 00 0Bextension_type = 0x000B,即 ec_point_formats
  • 00 02extension_data 的长度 = 0x0002 = 2 字节

接下来的 2 字节 就是 extension_data 的内容。


extension_data 的结构(ECPointFormatList)

RFC 4492 定义:

struct {
ECPointFormat ec_point_format_list<1..2^8-1>;
} ECPointFormatList;

enum {
uncompressed (0),
ansiX962_compressed_prime (1),
ansiX962_compressed_char2 (2),
reserved (248..255)
} ECPointFormat;

ec_point_format_list 是一个 变长向量,编码为:

  • 前 1 字节:列表的长度(以字节为单位,即后续格式值的个数)。
  • 紧接着是若干个 ECPointFormat 枚举值(每个 1 字节)。

    extension_data 接下来的 2 字节是:

    01   -- 列表长度:表示后面有 1 个格式值
    00 -- 第一个格式值:0x00(根据 RFC 表示「未压缩」格式)

    注意:RFC 4492 明确规定 0x00uncompressed0x01 是压缩格式(素数域)


为什么是这 2 字节而不是更多

  • 因为客户端只声明支持未压缩格式0x00),所以列表长度仅为 1
  • 一个格式值占 1 字节,因此总数据长度为 1(长度) + 1(值) = 2 字节,正好匹配扩展数据长度 00 02

如果客户端还支持压缩格式(0x01),则数据会变成 01 00 01?但注意:列表长度是字节计数,若支持两种格式(未压缩+压缩),则长度应为 2,后面跟 00 01。例如:02 00 01

常见误区和标准纠正

  • 0x00 = 未压缩0x01 = 压缩(素数域)
  • 现代 TLS 实现(包括 Go 标准库)通常只支持未压缩格式,因为压缩格式会增加实现复杂度和安全风险,且节省的带宽已不显著。

renegotiation_info

它完全遵循 RFC 5746(安全重协商扩展) 的定义。

FF01 -- 扩展类型:renegotiation_info (0xFF01)
0001 -- 扩展长度=1
00 --数据=0x00(不支持安全重协商)

整体扩展块结构

TLS 扩展的通用格式(RFC 5246):

struct {
ExtensionType extension_type; // 2 字节
opaque extension_data<0..2^16-1>; // 2 字节长度 + 数据
} Extension;

数据开头是:

  • FF 01extension_type = 0xFF01,即 renegotiation_info
  • 00 01extension_data 的长度 = 0x0001 = 1 字节

因此,接下来的 1 字节 就是 extension_data 的内容。


extension_data 的结构

RFC 5746 第 3.4 节定义了 renegotiation_info 的数据:

struct {
opaque renegotiated_connection<0..255>;
} RenegotiationInfo;

renegotiated_connection 是一个变长向量,编码为:

  • 前 1 字节:后续数据的长度(最大 255 字节)
  • 紧接着是 renegotiated_connection 的数据(长度由前一字节指定)

    extension_data 是:

    00   -- 表示 `renegotiated_connection` 的长度为 0(没有数据)

为什么长度是 1 字节,且数据为 0x00

  • 长度是 1 字节:因为 opaque <0..255> 向量使用 1 字节长度(而不是 2 字节),这是 RFC 5746 特意设计的小型向量。
  • 数据为 0x00:在初始握手(即尚未进行过任何重协商)中,renegotiated_connection 必须为空,所以长度字段为 0,不跟任何后续数据。

因此,extension_data 就是单个字节 0x00,扩展总长度 00 01 完全正确。


为什么是 FF 01 而不是 0x0017

扩展类型的值由 IANA 分配。标准扩展(如 SNI、supported_groups)使用 0x0000~0xFFFF 范围内的数值,但 renegotiation_info 被特意分配为 0xFF01(位于私有扩展区域,以防与旧版本冲突)。这是协议设计的一部分。


后续重协商时的情况

如果在这个连接上进行过重协商,该扩展会携带之前连接的安全绑定信息,长度和内容都会变化。但初始握手中,这个扩展就是 00,表示“客户端支持安全重协商,但当前连接尚未有重协商上下文”。

这是 TLS 初始握手中安全重协商扩展的标准编码,简单直接,只为标识双方支持此功能,不携带额外数据。


服务端响应

服务端响应数据如下:

160303005D020000590303BB454BCFD9C01225DA7B85F7D608747EB71D412EDB098E691CB12A866097D87520B0909337BF29D5EC7C153DA5F2787C990F04343B59285615FEA3FF8A31542938C02B000011FF0100010000000000000B00040300010216030302C60B0002C20002BF0002BC308202B83082025DA00302010202107C38DC2D583C511E7B0D9E14B5E6DD59300A06082A8648CE3D040302304431183016060355040A130F47534D204173736F63696174696F6E312830260603550403131F47534D204173736F63696174696F6E202D205253503220526F6F7420434931301E170D3235313230333030303030305A170D3237303130323233353935395A306D310B300906035504061302434E310E300C06035504070C05577548616E3131302F060355040A0C28577568616E205469616E797520496E666F726D6174696F6E20496E64757374727920434F2E4C5444311B301906035504030C122A2E6573696D2E776874792E636F6D2E636E3059301306072A8648CE3D020106082A8648CE3D0301070342000492CC5576A928203F807BEE009247AE2C0CCAE0367182874E59EB574135993A02104FC6DFB32CA237E1D8052D65A955E74CAE6D4EB99A46AC2F1B116F43030C59A38201063082010230140603551D20040D300B3009060767811201020103304D0603551D1F044630443042A040A03E863C687474703A2F2F67736D612D63726C2E73796D617574682E636F6D2F6F66666C696E6563612F67736D612D727370322D726F6F742D6369312E63726C30200603551D250101FF0416301406082B0601050507030106082B06010505070302300E0603551D0F0101FF04040302078030290603551D110422302082122A2E6573696D2E776874792E636F6D2E636E880A2B06010401829E000104301D0603551D0E041604141A35C19EBD10F3D65E0A5642787548CE8C54114C301F0603551D2304183016801481370F5125D0B1D408D4C3B232E6D25E795BEBFB300A06082A8648CE3D0403020349003046022100F00BD62C6813D7D9CDAE68B89356D2EC741770968DE8FA4F7D97C00E37F8B8D4022100A6C08D79E71C8F0CF1ED406894EEAFE81185A49CCD48B7450829B3C7C3A7734716030300940C000090030017410481DE95BD3AEF79A08FB862775055647B451213759F9FF0698FE1E16042599191E162139C82BEB74F6A5FC261B090CC99692244C26447C9CB74E3E8368724E610040300473045022060638E9211495FF3E53A0D7C7C0663621BA78F560A62506D8B0860A67AC732BD022100E5E9B851A222EB381B5727C748E7498EBACF4A78DAA847BBB32849A6DC06D7D316030300040E000000

可以把数据拆解为以下几段

段落1 — server_hello(2)

16
0303
005D
020000590303BB454BCFD9C01225DA7B85F7D608747EB71D412EDB098E691CB12A866097D87520B0909337BF29D5EC7C153DA5F2787C990F04343B59285615FEA3FF8A31542938C02B000011FF0100010000000000000B000403000102

段落2 — certificate(11)

16
0303
02C6
0B0002C20002BF0002BC308202B83082025DA00302010202107C38DC2D583C511E7B0D9E14B5E6DD59300A06082A8648CE3D040302304431183016060355040A130F47534D204173736F63696174696F6E312830260603550403131F47534D204173736F63696174696F6E202D205253503220526F6F7420434931301E170D3235313230333030303030305A170D3237303130323233353935395A306D310B300906035504061302434E310E300C06035504070C05577548616E3131302F060355040A0C28577568616E205469616E797520496E666F726D6174696F6E20496E64757374727920434F2E4C5444311B301906035504030C122A2E6573696D2E776874792E636F6D2E636E3059301306072A8648CE3D020106082A8648CE3D0301070342000492CC5576A928203F807BEE009247AE2C0CCAE0367182874E59EB574135993A02104FC6DFB32CA237E1D8052D65A955E74CAE6D4EB99A46AC2F1B116F43030C59A38201063082010230140603551D20040D300B3009060767811201020103304D0603551D1F044630443042A040A03E863C687474703A2F2F67736D612D63726C2E73796D617574682E636F6D2F6F66666C696E6563612F67736D612D727370322D726F6F742D6369312E63726C30200603551D250101FF0416301406082B0601050507030106082B06010505070302300E0603551D0F0101FF04040302078030290603551D110422302082122A2E6573696D2E776874792E636F6D2E636E880A2B06010401829E000104301D0603551D0E041604141A35C19EBD10F3D65E0A5642787548CE8C54114C301F0603551D2304183016801481370F5125D0B1D408D4C3B232E6D25E795BEBFB300A06082A8648CE3D0403020349003046022100F00BD62C6813D7D9CDAE68B89356D2EC741770968DE8FA4F7D97C00E37F8B8D4022100A6C08D79E71C8F0CF1ED406894EEAFE81185A49CCD48B7450829B3C7C3A77347

段落3 — server_key_exchange (12)

16
0303
0094
0C000090030017410481DE95BD3AEF79A08FB862775055647B451213759F9FF0698FE1E16042599191E162139C82BEB74F6A5FC261B090CC99692244C26447C9CB74E3E8368724E610040300473045022060638E9211495FF3E53A0D7C7C0663621BA78F560A62506D8B0860A67AC732BD022100E5E9B851A222EB381B5727C748E7498EBACF4A78DAA847BBB32849A6DC06D7D3

段落4 — server_hello_done(14)

16
0303
0004
0E000000

server_hello

当服务器能够找到一组可接受的算法时,它将发送此消息以响应ClientHello消息。如果它找不到这样的匹配,它将以握手失败警报进行响应。

对应的数据为

16
0303
005D
020000590303BB454BCFD9C01225DA7B85F7D608747EB71D412EDB098E691CB12A866097D87520B0909337BF29D5EC7C153DA5F2787C990F04343B59285615FEA3FF8A31542938C02B000011FF0100010000000000000B000403000102

数据定义为

struct {
ProtocolVersion server_version;
Random random;
SessionID session_id;
CipherSuite cipher_suite;
CompressionMethod compression_method;
select (extensions_present) {
case false:
struct {};
case true:
Extension extensions<0..2^16-1>;
};
} ServerHello;

struct {
uint8 major;
uint8 minor;
} ProtocolVersion;

struct {
uint32 gmt_unix_time;
opaque random_bytes[28];
} Random;

opaque SessionID<0..32>;

uint8 CipherSuite[2]; /* Cryptographic suite selector */

enum { null(0), (255) } CompressionMethod;

该结构与Client Hello结构类似,但是注意Client Hello结构为

struct {
ProtocolVersion client_version;
Random random;
SessionID session_id;
CipherSuite cipher_suites<2..2^16-2>;
CompressionMethod compression_methods<1..2^8-1>;
select (extensions_present) {
case false:
struct {};
case true:
Extension extensions<0..2^16-1>;
};
} ClientHello;

struct {
uint8 major;
uint8 minor;
} ProtocolVersion;

struct {
uint32 gmt_unix_time;
opaque random_bytes[28];
} Random;

opaque SessionID<0..32>;

uint8 CipherSuite[2]; /* Cryptographic suite selector */

enum { null(0), (255) } CompressionMethod;

在Client Hello结构中cipher_suites和compression_methods是边长的。

16  -- record.type # 内容类型:Handshake = 22 (0x16)
0303 -- record.version 协议版本:TLS 1.2
005D -- record.length 记录长度
02 --Handshake.msg_type # server_hello(2) 握手消息类型
000059 -- -- Handshake.length 消息长度(3字节大端序)
0303 -- ServerHello.client_version 协议版本:TLS 1.2 (0x0303)
BB454BCFD9C01225DA7B85F7D608747EB71D412EDB098E691CB12A866097D875 -- ServerHello.random #服务器随机数(第3-34字节,共32字节)
20 B0909337BF29D5EC7C153DA5F2787C990F04343B59285615FEA3FF8A31542938 -- ServerHello.session_id #解析会话ID
C02B -- ServerHello.cipher_suite #协商的密码套件(2字节大端序)
00 -- ServerHello.compression_method #协商的密码套件(2字节大端序)
0011 -- ServerHello.extensions_length #扩展长度(2字节大端序)
FF0100010000000000000B000403000102

certificate

当服务器能够找到一组可接受的算法时,它将发送此消息以响应ClientHello消息。如果它找不到这样的匹配,它将以握手失败警报进行响应。

结构定义为

opaque ASN.1Cert<1..2^24-1>;

struct {
ASN.1Cert certificate_list<0..2^24-1>;
} Certificate;

示例数据为

16
0303
02C6
0B0002C20002BF0002BC308202B83082025DA00302010202107C38DC2D583C511E7B0D9E14B5E6DD59300A06082A8648CE3D040302304431183016060355040A130F47534D204173736F63696174696F6E312830260603550403131F47534D204173736F63696174696F6E202D205253503220526F6F7420434931301E170D3235313230333030303030305A170D3237303130323233353935395A306D310B300906035504061302434E310E300C06035504070C05577548616E3131302F060355040A0C28577568616E205469616E797520496E666F726D6174696F6E20496E64757374727920434F2E4C5444311B301906035504030C122A2E6573696D2E776874792E636F6D2E636E3059301306072A8648CE3D020106082A8648CE3D0301070342000492CC5576A928203F807BEE009247AE2C0CCAE0367182874E59EB574135993A02104FC6DFB32CA237E1D8052D65A955E74CAE6D4EB99A46AC2F1B116F43030C59A38201063082010230140603551D20040D300B3009060767811201020103304D0603551D1F044630443042A040A03E863C687474703A2F2F67736D612D63726C2E73796D617574682E636F6D2F6F66666C696E6563612F67736D612D727370322D726F6F742D6369312E63726C30200603551D250101FF0416301406082B0601050507030106082B06010505070302300E0603551D0F0101FF04040302078030290603551D110422302082122A2E6573696D2E776874792E636F6D2E636E880A2B06010401829E000104301D0603551D0E041604141A35C19EBD10F3D65E0A5642787548CE8C54114C301F0603551D2304183016801481370F5125D0B1D408D4C3B232E6D25E795BEBFB300A06082A8648CE3D0403020349003046022100F00BD62C6813D7D9CDAE68B89356D2EC741770968DE8FA4F7D97C00E37F8B8D4022100A6C08D79E71C8F0CF1ED406894EEAFE81185A49CCD48B7450829B3C7C3A77347

解析为

16 -- record.type # 内容类型:Handshake = 22 (0x16)
0303 -- record.version 协议版本:TLS 1.2
02C6 -- record.length 记录长度
0B --Handshake.msg_type # certificate(11) 握手消息类型
0002C2 -- Handshake.length 消息长度(3字节大端序)
0002BF -- certificate_list 的长度 = 0x02BF = 703 字节
0002BC -- 第一个证书的长度 = 0x02BC = 700 字节
308202B83082025DA00302010202107C38DC2D583C511E7B0D9E14B5E6DD59300A06082A8648CE3D040302304431183016060355040A130F47534D204173736F63696174696F6E312830260603550403131F47534D204173736F63696174696F6E202D205253503220526F6F7420434931301E170D3235313230333030303030305A170D3237303130323233353935395A306D310B300906035504061302434E310E300C06035504070C05577548616E3131302F060355040A0C28577568616E205469616E797520496E666F726D6174696F6E20496E64757374727920434F2E4C5444311B301906035504030C122A2E6573696D2E776874792E636F6D2E636E3059301306072A8648CE3D020106082A8648CE3D0301070342000492CC5576A928203F807BEE009247AE2C0CCAE0367182874E59EB574135993A02104FC6DFB32CA237E1D8052D65A955E74CAE6D4EB99A46AC2F1B116F43030C59A38201063082010230140603551D20040D300B3009060767811201020103304D0603551D1F044630443042A040A03E863C687474703A2F2F67736D612D63726C2E73796D617574682E636F6D2F6F66666C696E6563612F67736D612D727370322D726F6F742D6369312E63726C30200603551D250101FF0416301406082B0601050507030106082B06010505070302300E0603551D0F0101FF04040302078030290603551D110422302082122A2E6573696D2E776874792E636F6D2E636E880A2B06010401829E000104301D0603551D0E041604141A35C19EBD10F3D65E0A5642787548CE8C54114C301F0603551D2304183016801481370F5125D0B1D408D4C3B232E6D25E795BEBFB300A06082A8648CE3D0403020349003046022100F00BD62C6813D7D9CDAE68B89356D2EC741770968DE8FA4F7D97C00E37F8B8D4022100A6C08D79E71C8F0CF1ED406894EEAFE81185A49CCD48B7450829B3C7C3A77347

server_key_exchange

此消息将在服务器证书消息(或ServerHello消息,如果这是匿名协商)之后立即发送。只有当服务器证书消息(如果已发送)包含的数据不足以允许客户端交换预主密钥时,服务器才会发送ServerKeyExchange消息。以下密钥交换方法也是如此

示例数据

16
0303
0094
0C000090030017410481DE95BD3AEF79A08FB862775055647B451213759F9FF0698FE1E16042599191E162139C82BEB74F6A5FC261B090CC99692244C26447C9CB74E3E8368724E610040300473045022060638E9211495FF3E53A0D7C7C0663621BA78F560A62506D8B0860A67AC732BD022100E5E9B851A222EB381B5727C748E7498EBACF4A78DAA847BBB32849A6DC06D7D3

数据结构为

enum { dhe_dss, dhe_rsa, dh_anon, rsa, dh_dss, dh_rsa
/* may be extended, e.g., for ECDH -- see [TLSECC] */
} KeyExchangeAlgorithm;

struct {
opaque dh_p<1..2^16-1>;
opaque dh_g<1..2^16-1>;
opaque dh_Ys<1..2^16-1>;
} ServerDHParams; /* Ephemeral DH parameters */

struct {
select (KeyExchangeAlgorithm) {
case dh_anon:
ServerDHParams params;
case dhe_dss:
case dhe_rsa:
ServerDHParams params;
digitally-signed struct {
opaque client_random[32];
opaque server_random[32];
ServerDHParams params;
} signed_params;
case rsa:
case dh_dss:
case dh_rsa:
struct {} ;
/* message is omitted for rsa, dh_dss, and dh_rsa */
/* may be extended, e.g., for ECDH -- see [TLSECC] */
};
} ServerKeyExchange;

先解析为

16 -- record.type # 内容类型:Handshake = 22 (0x16)
0303 -- record.version 协议版本:TLS 1.2
0094 -- record.length 记录长度
0C --Handshake.msg_type # server_key_exchange 握手消息类型
000090 -- Handshake.length 消息长度(3字节大端序)
030017410481DE95BD3AEF79A08FB862775055647B451213759F9FF0698FE1E16042599191E162139C82BEB74F6A5FC261B090CC99692244C26447C9CB74E3E8368724E610040300473045022060638E9211495FF3E53A0D7C7C0663621BA78F560A62506D8B0860A67AC732BD022100E5E9B851A222EB381B5727C748E7498EBACF4A78DAA847BBB32849A6DC06D7D3

对于ServerKeyExchange 消息体,可见 https://datatracker.ietf.org/doc/html/rfc4492

ServerKeyExchange 消息体

对于 ECDHE,消息体结构为(RFC 4492):

text

struct {
ECParameters curve_params;
ECPoint public;
Signature signed_params;
} ServerECDHParams;

其中:

  • ECParameters 包含 curve_typenamedcurve

  • ECPoint 是变长向量(带长度前缀)

  • Signature 在 TLS 1.2 中为:

    text

    struct {
    SignatureAndHashAlgorithm algorithm; // 2字节
    opaque signature<0..2^16-1>; // 2字节长度 + 数据g
    }

完整定义为

struct {
ECCurveType curve_type;
select (curve_type) {
case explicit_prime:
opaque prime_p <1..2^8-1>;
ECCurve curve;
ECPoint base;
opaque order <1..2^8-1>;
opaque cofactor <1..2^8-1>;
case explicit_char2:
uint16 m;
ECBasisType basis;
select (basis) {
case ec_trinomial:
opaque k <1..2^8-1>;
case ec_pentanomial:
opaque k1 <1..2^8-1>;
opaque k2 <1..2^8-1>;
opaque k3 <1..2^8-1>;
};
ECCurve curve;
ECPoint base;
opaque order <1..2^8-1>;
opaque cofactor <1..2^8-1>;
case named_curve:
NamedCurve namedcurve;
};
} ECParameters;

struct {
opaque point <1..2^8-1>;
} ECPoint;

struct {
ECParameters curve_params;
ECPoint public;
} ServerECDHParams;

可进一步解析为

16 -- record.type # 内容类型:Handshake = 22 (0x16)
0303 -- record.version 协议版本:TLS 1.2
0094 -- record.length 记录长度
0C --Handshake.msg_type # server_key_exchange 握手消息类型
000090 -- Handshake.length 消息长度(3字节大端序)
03 -- 表示 named_curve(RFC 4492 定义)
0017 -- 曲线编号,对应 secp256r1(NIST P-256)
41 -- 公钥数据为 0x41 = 65 字节
04 -- 04 表示 未压缩格式
81DE95BD3AEF79A08FB862775055647B451213759F9FF0698FE1E16042599191 -- 32 字节为 X 坐标
E162139C82BEB74F6A5FC261B090CC99692244C26447C9CB74E3E8368724E610 -- 32 字节为 Y 坐标

0403 -- SHA256+ECDSA
0047 -- 签名数据长度
3045022060638E9211495FF3E53A0D7C7C0663621BA78F560A62506D8B0860A67AC732BD022100E5E9B851A222EB381B5727C748E7498EBACF4A78DAA847BBB32849A6DC06D7D3

server_hello_done

ServerHelloDone消息由服务器发送,以指示ServerHello和相关消息的结束。发送此消息后,服务器将等待客户端响应。

示例数据为

16030300040E000000

结构为

struct { } ServerHelloDone;

解析结果

16 -- record.type # 内容类型:Handshake = 22 (0x16)
0303 -- record.version 协议版本:TLS 1.2
0004 -- record.length 记录长度
0E --Handshake.msg_type # server_hello_done 握手消息类型
000000 --Handshake.length 握手消息长度

客户端响应

Client Key Exchange Message

此消息始终由客户端发送。如果发送了客户端证书消息,它必须立即跟在其后。否则,它必须是客户端在收到ServerHelloDone消息后发送的第一条消息。

示例数据为

1603030046100000424104B308E8FC325436A49506BFD84D6CDCC75B16921762DEBB4298C069F83F0200000A2F059C68EF2E6ED078BCE403945C72C0248F2298E39DCDAFC12126F055D671

数据结构为

struct {
select (KeyExchangeAlgorithm) {
case rsa:
EncryptedPreMasterSecret;
case dhe_dss:
case dhe_rsa:
case dh_dss:
case dh_rsa:
case dh_anon:
ClientDiffieHellmanPublic;
} exchange_keys;
} ClientKeyExchange;

RSA-Encrypted Premaster Secret Message

struct {
ProtocolVersion client_version;
opaque random[46];
} PreMasterSecret;

client_version
The latest (newest) version supported by the client. This is
used to detect version rollback attacks.

random
46 securely-generated random bytes.

struct {
public-key-encrypted PreMasterSecret pre_master_secret;
} EncryptedPreMasterSecret;

pre_master_secret
This random value is generated by the client and is used to
generate the master secret, as specified in Section 8.1.

Client Diffie-Hellman Public Value

enum { implicit, explicit } PublicValueEncoding;

implicit
If the client has sent a certificate which contains a suitable
Diffie-Hellman key (for fixed_dh client authentication), then
Yc is implicit and does not need to be sent again. In this
case, the client key exchange message will be sent, but it MUST
be empty.

explicit
Yc needs to be sent.

struct {
select (PublicValueEncoding) {
case implicit: struct { };
case explicit: opaque dh_Yc<1..2^16-1>;
} dh_public;
} ClientDiffieHellmanPublic;

dh_Yc
The client's Diffie-Hellman public value (Yc).

解析后的数据为

16  -- record.type # 内容类型:Handshake = 22 (0x16)
0303 -- record.version 协议版本:TLS 1.2
0046 -- record.length 记录长度
10 --Handshake.msg_type # client_key_exchange(16) 握手消息类型
000042 -- Handshake.length 消息长度(3字节大端序)
41 -- 值为 0x41 = 65表示后续公钥数据的长度为 65 字节
04 --开头 04 表示 未压缩格式(uncompressed)
B308E8FC325436A49506BFD84D6CDCC75B16921762DEBB4298C069F83F020000 -- 32 字节是 X 坐标
0A2F059C68EF2E6ED078BCE403945C72C0248F2298E39DCDAFC12126F055D671 --32 字节是 Y 坐标

ChangeCipherSpec

存在改变密码规范协议,以表示加密策略中的转换。该协议由一条消息组成,该消息在当前(而非挂起)连接状态下进行加密和压缩。该消息由一个值为1的字节组成

客户端和服务器都会发送ChangeCipherSpec消息,通知接收方后续记录将受到新协商的CipherSpec和密钥的保护。接收到此消息后,接收器会指示记录层立即将读取的挂起状态复制到读取的当前状态。发送此消息后,发送方必须立即指示记录层将写挂起状态设置为写活动状态。

(见第6.1节。)在安全参数达成一致后,但在发送验证完成消息之前,在握手期间发送ChangeCipherSpec消息。

注意:如果在连接上传输数据时发生重新握手,通信方可能会继续使用旧的CipherSpec发送数据。但是,一旦发送了ChangeCipherSpec,就必须使用新的CipherSpec。发送ChangeCipherSpec的第一方不知道另一方已经完成了新密钥材料的计算(例如,如果它必须执行耗时的公钥操作)。因此,可能存在一个小的时间窗口,在此期间,接收者必须缓冲数据。在实践中,对于现代机器来说,这个间隔可能相当短。

示例数据为

140303000101

数据结构为

struct {
enum { change_cipher_spec(1), (255) } type;
} ChangeCipherSpec;

解析为

14  -- record.type # 内容类型:change_cipher_spec(20)
0303 -- record.version 协议版本:TLS 1.2
0001 -- record.length 记录长度
01 -- change_cipher_spec(1)

为什么要发送 ChangeCipherSpec?

它是在 TLS 握手过程中 切换加密模式 的信号:

  • 在握手的前半段(ClientHello 到 ServerHelloDone),所有消息都是明文传输。
  • 当双方完成密钥交换并派生出 master_secret 以及读写密钥后,需要通知对端:“从现在开始,我要用刚协商好的密钥加密后续消息了”。
  • ChangeCipherSpec 就是这个通知,它本身始终以明文发送(不受当前加密状态影响),但它的出现标志着发送方将立即切换到新密钥状态。

因此,它是一道分界线:在这条消息之后,Finished 及所有应用数据都将被加密。


如果不发送会怎样?

  • 接收方(服务器)会一直停留在明文模式,继续等待明文握手消息。
  • 而发送方(客户端)如果接着发送加密的 Finished,服务器将无法正确解密(因为服务器未切换密钥,会尝试用旧状态或明文解析),导致解密失败解析错位,最终握手失败并触发警报(如 bad_record_macunexpected_message)。
  • 同样,服务器也必须在发送完 ServerHelloDone 后,接收客户端的 ChangeCipherSpec 后再发送自己的 ChangeCipherSpec 和 Finished,否则双方状态不一致。

140303000101 完美对应 ChangeCipherSpec 结构:记录头 + 一个字节的 type=1。它的作用是切换加密状态,确保后续消息受到保护。不发送会导致对方无法正确解密后续消息,握手必然失败

Finished

始终在更改密码规范消息后立即发送完成消息,以验证密钥交换和身份验证过程是否成功。在其他握手消息和完成消息之间接收更改密码规范消息至关重要。

完成消息是第一个使用刚刚协商的算法、密钥和秘密保护的消息。已完成消息的收件人必须验证内容是否正确。一旦一方发送了其完成消息,并从其对等方接收并验证了完成消息,它就可以开始通过连接发送和接收应用程序数据。

示例数据

160303002800000000000000006C1185C0668517CEDDCE7D3D605B29EB1A96A89C89EDE3015A51B44B51248BE5

数据结构为

struct {
opaque verify_data[verify_data_length];
} Finished;

verify_data
PRF(master_secret, finished_label, Hash(handshake_messages))
[0..verify_data_length-1];

finished_label
For Finished messages sent by the client, the string
"client finished". For Finished messages sent by the server,
the string "server finished".

哈希表示握手消息的哈希。对于第5节中定义的PRF,哈希必须是用作PRF基础的哈希。任何定义不同PRF的密码套件也必须定义在完成计算中使用的哈希。在以前版本的TLS中,verify_data的长度始终为12个八位字节。在当前版本的TLS中,它取决于密码套件。任何没有明确指定verify_data_length的密码套件的verify_data_length都等于12。这包括所有现有的密码套件。请注意,此表示与以前的版本具有相同的编码。未来的密码套件可以指定其他长度,但此类长度必须至少为12个字节。

此握手中所有消息的所有数据(不包括任何HelloRequest消息),直到但不包括此消息。这只是握手层可见的数据,不包括记录层标头。这是迄今为止交换的第7.4节中定义的所有握手结构的连接。

如果在握手的适当时刻,Finish消息前面没有ChangeCipherSpec消息,则这是一个致命的错误。handshake_messages值包括从ClientHello开始的所有握手消息,包括但不包括此Finish消息。这可能与第7.4.8节中的握手消息不同,因为它将包括CertificateVerify消息(如果发送)。此外,客户端发送的Finish消息的handshake_message将与服务器发送的Finished消息不同,因为第二次发送的消息将包括前一个消息。注意:ChangeCipherSpec消息、警报和任何其他记录类型都不是握手消息,也不包含在哈希计算中。此外,握手哈希中省略了HelloRequest消息。

解密为

16   -- record.type # 内容类型:Handshake = 22 (0x16)
0303 -- record.version 协议版本:TLS 1.2
0028 -- record.length 记录长度
00000000000000006C1185C0668517CEDDCE7D3D605B29EB1A96A89C89EDE3015A51B44B51248BE5 -- 加密后的握手消息

负载(40 字节)的含义

负载是密文,它包含:

  • 显式 IV / Nonce(对于 CBC 是 16 字节 IV,对于 GCM 是 8 字节显式 nonce)
  • 密文 + 认证标签(GCM)或 密文 + MAC(CBC)

数据中前 6 个字节是 00 00 00 00 00 00,看起来不像随机 IV,更可能是 GCM 的显式 nonce(序列号),但通常序列号是 8 字节,这里是 6 个 0 后面还有 6C...,可能序列号是 8 字节,即前 8 字节为 00 00 00 00 00 00 6C 11?实际上,我们无法准确判断,因为不知道具体密码套件。

但重点是:这 40 字节不是 Finished 消息本身,而是它的密文

解密后得到真正的 Finished 握手消息

解密后,我们预期得到一条明文握手消息:

  • 类型:0x14(Finished)
  • 长度:0x00000C(12 字节,因为 verify_data 长度固定为 12)
  • 消息体:12 字节的 verify_data

根据 RFC 5246,Finished 结构非常简单:

struct {
opaque verify_data[verify_data_length];
} Finished;

只有 verify_data 数组,没有其他字段。但在握手消息中,它仍然带有握手头(类型+长度),而消息体就是 verify_data

因此,Finished 消息体的完整二进制就是 12 字节的 verify_data。在握手消息中,它被包装为:

14 00 00 0C [12字节verify_data]

其中 14 是类型,00 00 0C 是长度。


如何从数据对应到结构?

  • 记录层16 03 03 00 28 指出这是一个 Handshake 记录,负载长度为 40 字节。
  • 负载:经过解密后,应得到 14 00 00 0C [12字节]
  • Finished 结构:消息体就是 [12字节],即 verify_data
  • verify_data 的值由 PRF 计算得出,不直接出现在数据中,而是经过加密后发送。

如果不加密,Finished 会是什么样子?

ChangeCipherSpec 之前,握手消息都是明文的,但 Finished 必须在加密后发送(因为它的作用是验证加密通道的完整性),所以它总是密文。因此,看到的数据不是明文 Finished,而是加密后的记录。

服务器二次响应

ChangeCipherSpec

示例数据为

14  -- record.type # 内容类型:change_cipher_spec(20)
0303 -- record.version 协议版本:TLS 1.2
0001 -- record.length 记录长度
01 -- change_cipher_spec(1)

Finished

示例数据

16   -- record.type # 内容类型:Handshake = 22 (0x16)
0303 -- record.version 协议版本:TLS 1.2
0028 -- record.length 记录长度
012CAD12A6015C8976057D2DB2439626E4691C63E66FFEE46643C0411E8B2A14A1F70057CE6F92D1 -- 加密后的握手消息

Application Data Protocol

应用程序数据消息由记录层承载,并根据当前连接状态进行分段、压缩和加密。消息被视为记录层的透明数据。

客户端发送给服务器

示例数据

17030300710000000000000001C5262D872FA87D2D9DB92F44FF019A2EBF1B375C8593DB679F8A34440D4533E6844F0A0308BEE20006408B51636975E990B18E032B502E50DD1888D3F7CB27701F2B04DA864CA73D2986FAB6504BC20678FE4421B047FA373288EF43F8F0D37114C9286880383189FE

解析为

17 -- record.type # 内容类型:application_data(23)
0303 -- record.version 协议版本:TLS 1.2
0071 -- record.length 记录长度
0000000000000001C5262D872FA87D2D9DB92F44FF019A2EBF1B375C8593DB679F8A34440D4533E6844F0A0308BEE20006408B51636975E990B18E032B502E50DD1888D3F7CB27701F2B04DA864CA73D2986FAB6504BC20678FE4421B047FA373288EF43F8F0D37114C9286880383189FE

服务器发送给客户端

示例数据

1703030231012CAD12A6015C8ADD5995A6BBAC798F1E2C0C1A9D71571A4E7C47793C2D2AEDDA587F5B9B9B1B7AB1FA4D16C9DD10B0E7289F8A7D7C5DBC947D60FF23E7A0952765CFEF0DAE68B7AF1446ABC4E6D8D79319BE8E067BB40E5721BD643770F7243C4FD39643277B3585D0755FD083709C58E14BE663BED27FEACB6B07392CDA9EE7BBFD70012B7926B8F96EDCB410BC2912ED9CE5FB88B2C1A984F609D9D8467E25D30AFB920B378B0244E7E2408C63D31E9F360A59446A33411C22917537BBC8B02E1AFA74D535C6544B2A76DDC46F0C67F3DD60103C6BE931AC0701C887162C6DBBE3CF1AF32B9C6A511984D2F692C1209BEDBEC75F2FBE041F144D04BEC9CC183D55A3E0B9F7E5935A21EF60B8B6F21280A6F175518E9197E21CA5E52A9B88D1866618B6E2C4EC69EB5CC034103A8921432FE96839B4498DF8C60DAF5B9198EA9C40157BA9491723A34F1538B63F22067CEF4C0FAF02AECA811AD254D784BDEF4D6D50347D1A45B6D6054C43B92F82FFF01F0FD460B127658E52F8EF6A6211699AEB3329C72C25925D7F89AFD2B44B3A1A104BCDCF84F831EBE6A15AA240294B0BC768534194FEBB09B692A091B1D66E420CC16E8F2564302A37E666427D41FF285B3C39F921FA881CAF6B0ACA4005D72C516507A9F863587CB9C9F0F531FABF736BB6614980107E49BEA1539ECE18755FE0E9B2A1D53FA5E43AAA8569808E1E2D8725BE67B92FFCEFFEF15B4D74CA836A7CAEC4768688A5E0ECD847710B94C4D8EF413191C58D0C33F0AC8B01C6CF72

解析为

17  -- record.type # 内容类型:application_data(23)
0303 -- record.version 协议版本:TLS 1.2
0231 -- record.length 记录长度
012CAD12A6015C8ADD5995A6BBAC798F1E2C0C1A9D71571A4E7C47793C2D2AEDDA587F5B9B9B1B7AB1FA4D16C9DD10B0E7289F8A7D7C5DBC947D60FF23E7A0952765CFEF0DAE68B7AF1446ABC4E6D8D79319BE8E067BB40E5721BD643770F7243C4FD39643277B3585D0755FD083709C58E14BE663BED27FEACB6B07392CDA9EE7BBFD70012B7926B8F96EDCB410BC2912ED9CE5FB88B2C1A984F609D9D8467E25D30AFB920B378B0244E7E2408C63D31E9F360A59446A33411C22917537BBC8B02E1AFA74D535C6544B2A76DDC46F0C67F3DD60103C6BE931AC0701C887162C6DBBE3CF1AF32B9C6A511984D2F692C1209BEDBEC75F2FBE041F144D04BEC9CC183D55A3E0B9F7E5935A21EF60B8B6F21280A6F175518E9197E21CA5E52A9B88D1866618B6E2C4EC69EB5CC034103A8921432FE96839B4498DF8C60DAF5B9198EA9C40157BA9491723A34F1538B63F22067CEF4C0FAF02AECA811AD254D784BDEF4D6D50347D1A45B6D6054C43B92F82FFF01F0FD460B127658E52F8EF6A6211699AEB3329C72C25925D7F89AFD2B44B3A1A104BCDCF84F831EBE6A15AA240294B0BC768534194FEBB09B692A091B1D66E420CC16E8F2564302A37E666427D41FF285B3C39F921FA881CAF6B0ACA4005D72C516507A9F863587CB9C9F0F531FABF736BB6614980107E49BEA1539ECE18755FE0E9B2A1D53FA5E43AAA8569808E1E2D8725BE67B92FFCEFFEF15B4D74CA836A7CAEC4768688A5E0ECD847710B94C4D8EF413191C58D0C33F0AC8B01C6CF72

CertificateApplication Data 消息超大

从 RFC 规范的角度来看,CertificateApplication Data 这两种消息在数据量超大时的处理方式,其核心机制都是 “分片”

这个机制由 TLS 记录层(Record Layer)统一提供,规定任何消息在通过记录层发送时,都必须被切分成不超过 2^14 字节(即 16 KB) 的数据块。

核心规则:记录层分片

无论是握手消息还是应用数据,在通过记录层发送时都必须遵循一个统一的硬性限制:单个 TLS 记录(TLSPlaintext)的数据长度不得超过 2^14 字节(16 KB)

因此,所有超过此大小的消息都必须被拆分成多个记录来发送。这是所有处理方式的共同基础。

区分:Certificate 与 Application Data

虽然都基于分片,但由于消息性质不同,两者在处理规则上存在关键差异。

Certificate 消息:有序的握手分片

Certificate 消息属于握手协议,其分片过程受到严格约束,需要保证消息的完整性。

  • 必须连续:如果一个握手消息(如 Certificate)被分片,那么承载这些分片的多个 TLS 记录必须是连续的,中间不能夹杂其他类型的记录
  • 不能跨越密钥变更:分片后的握手消息不能跨越密钥变更(key change) 的边界。这意味着在 ChangeCipherSpec 消息前后,握手消息必须完整地结束或开始,不能有分片横跨这个边界。
  • 实现要求:RFC 明确指出,像 certificate 这样的消息可能非常大,以至于需要进行分片。一个健壮的实现必须能正确处理被分片到多个 TLS 记录中的握手消息

Application Data 消息:灵活的透明数据

与握手消息的严格约束不同,应用数据的分片规则要灵活得多。

  • 可以任意分片Application Data 消息可以被拆分成多个记录进行发送。
  • 可以合并发送:多个小的应用数据片段也可以被合并到同一个 TLS 记录中发送。
  • 允许零长度分片:允许发送零长度Application Data 记录,这可以作为流量分析的对抗手段。
  • 透明处理Application Data 的内容对 TLS 记录层是透明的,记录层仅负责分片、压缩和加密。

总结对比

特性 Certificate (握手消息) Application Data
处理方式 分片 (Fragmented) 到多个记录 分片 (Fragmented)合并 (Coalesced)
记录约束 必须连续,中间不能有其它类型记录 无特殊连续性要求
密钥变更 不能跨越密钥变更边界 无此限制
零长度 禁止发送零长度分片 允许发送零长度分片
本质 结构化的协议消息,需保证完整性 透明的上层数据流,处理更灵活

总而言之,RFC 规范为 TLS 记录层设计了一套统一的分片机制来处理大消息,但针对 Certificate 这类关键的握手消息和 Application Data 这种灵活的数据流,在具体的连续性、边界对齐等规则上做了细致的区分。

Application Data Protocol消息超大

不能在一个 TLS 记录(包)里无限制发送。TLS 协议对单个记录的有效载荷有明确的长度限制(最大 16KB),更大的应用数据必须拆分成多个记录。

协议规定的硬性限制

根据 RFC 5246(TLS 1.2)第 6.2.3 节

The record layer fragments information blocks into TLSPlaintext records carrying data in chunks of 2^14 bytes or less.

  • 最大值16,384 字节(即 16KB)
  • 这是 TLSPlaintext(加密前明文)的长度上限。如果应用数据(如 HTTP 响应体)超过 16KB,TLS 层必须自动将其分片为多个 16KB 或更小的记录块。

另外,记录层头部的 length 字段虽然占 2 字节(理论最大 65,535 字节),但标准实现强制限制为 16KB,以保证安全性和缓冲区的合理管理。


为什么不设计成无限大(或更大)

  • 缓冲区管理:限制记录大小简化了内存分配,避免为一次性接收巨量数据而申请超大缓冲区(防止 DoS 攻击)。
  • 实时性与延迟:16KB 是兼顾网络吞吐和低延迟的折中值。若记录过大,接收端必须收完整条记录才能解密,增加延迟;拆分后可以边收边解。
  • 重传效率:TCP 丢包时,只需重传损坏的小记录,而不是整个大块。
  • 标准兼容性:所有 TLS 实现(包括 OpenSSL、Go 标准库)都遵循这个上限,保证了互通性。

实际处理方式(在代码中的体现)

  • 发送端(如 sendRecord 函数): 如果数据超过 16KB,不能直接调用一次 sendRecord。上层协议(如 HTTP/1.1 或 HTTP/2)通常会自己分块,或由 TLS 库(标准库)内部循环发送多个记录。
  • 接收端(如 recvRecord 循环): 读取多个记录,逐个解密并拼接明文。 在主函数中接收 HTTP 响应时,也是用 for 循环持续读取记录,直到连接关闭(或收到 close_notify),这正是为应对多条分片记录而设计的。

需要注意的“包”和“记录”区别

  • TLS 记录是协议层的逻辑单元。
  • 网络包(IP 包或 TCP 段) 是传输层的物理单元。 由于 TCP 是流协议,一个 TLS 记录可能被拆分到多个 TCP 段发送,反之,一个 TCP 段也可能包含多个小的 TLS 记录。因此,“一个包发完”在 TLS 层面没有意义——即使数据小于 16KB,它也可能在 TCP 层被分包;即使数据很大,TLS 也会通过多个记录承载。
情况 处理方式
数据 ≤ 16KB 封装在 1 个 TLS 记录中发送(但 TCP 层仍可能分包)。
数据 > 16KB 拆分成 多个 TLS 记录依次发送(每个记录长度 ≤ 16KB)。

所以,不能在单个 TLS 记录里发送超大消息,但可以在一个 TLS 连接中通过多个记录连续发送完整数据。