Skip to main content

5. Record Protocol (记录协议)

TLS记录协议接收要传输的消息,将数据分段为可管理的块,保护记录,然后传输结果。接收到的数据经过验证、解密、重新组装,然后交付给更高级别的客户端。

TLS记录是有类型的,这允许多个更高级别的协议在相同的记录层上多路复用。本文档指定了四种内容类型:握手 (handshake)、应用数据 (application_data)、警报 (alert) 和更改密码规范 (change_cipher_spec)。change_cipher_spec记录仅用于兼容性目的(参见附录D.4)。

实现可以 (MAY) 在发送或接收第一个ClientHello消息之后和接收对等体的Finished消息之前的任何时间接收到由单字节值0x01组成的类型为change_cipher_spec的未加密记录,并且必须 (MUST) 简单地丢弃它而不进行进一步处理。请注意,此记录可能在握手的某个点出现,在该点实现期望受保护的记录,因此在尝试解保护记录之前检测此条件是必要的。接收任何其他change_cipher_spec值或接收受保护的change_cipher_spec记录的实现必须 (MUST) 使用"unexpected_message"警报中止握手。如果实现检测到在第一个ClientHello消息之前或在对等体的Finished消息之后接收到的change_cipher_spec记录,它必须 (MUST) 将其视为意外的记录类型(尽管无状态服务器可能无法区分这些情况与允许的情况)。

实现不得 (MUST NOT) 发送本文档中未定义的记录类型,除非通过某些扩展进行协商。如果TLS实现接收到意外的记录类型,它必须 (MUST) 使用"unexpected_message"警报终止连接。新的记录内容类型值由IANA在TLS ContentType注册表中分配,如第11节所述。

5.1. Record Layer (记录层)

记录层将信息块分段为TLSPlaintext记录,以2^14字节或更少的块携带数据。消息边界的处理方式取决于底层的ContentType。任何未来的内容类型必须 (MUST) 指定适当的规则。请注意,这些规则比TLS 1.2中强制执行的规则更严格。

握手消息可以 (MAY) 合并到单个TLSPlaintext记录中,或跨多个记录分段,前提是:

  • 握手消息不得 (MUST NOT) 与其他记录类型交错。也就是说,如果握手消息分散在两个或多个记录中,则它们之间不得 (MUST NOT) 有任何其他记录。

  • 握手消息不得 (MUST NOT) 跨密钥更改。实现必须 (MUST) 验证密钥更改之前的所有消息是否与记录边界对齐;如果不对齐,则它们必须 (MUST) 使用"unexpected_message"警报终止连接。因为ClientHello、EndOfEarlyData、ServerHello、Finished和KeyUpdate消息可以紧接在密钥更改之前,所以实现必须 (MUST) 将这些消息与记录边界对齐发送。

实现不得 (MUST NOT) 发送握手类型的零长度片段,即使这些片段包含填充。

警报消息(第6节)不得 (MUST NOT) 跨记录分段,并且多个警报消息不得 (MUST NOT) 合并到单个TLSPlaintext记录中。换句话说,具有Alert类型的记录必须 (MUST) 恰好包含一条消息。

应用数据消息包含对TLS不透明的数据。应用数据消息始终受保护。可以 (MAY) 发送应用数据的零长度片段,因为它们可能作为流量分析对策很有用。应用数据片段可以 (MAY) 跨多个记录拆分或合并到单个记录中。

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

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

type: 用于处理封闭片段的更高级别协议。

legacy_record_version: 对于TLS 1.3实现生成的所有记录,必须 (MUST) 设置为0x0303,但初始ClientHello(即,不是在HelloRetryRequest之后生成的)除外,在该情况下它也可以 (MAY) 是0x0301以实现兼容性目的。此字段已弃用,并且必须 (MUST) 在所有目的上被忽略。TLS的先前版本在某些情况下会在此字段中使用其他值。

length: 以下TLSPlaintext.fragment的长度(以字节为单位)。长度不得 (MUST NOT) 超过2^14字节。接收超过此长度的记录的端点必须 (MUST) 使用"record_overflow"警报终止连接。

fragment: 正在传输的数据。此值是透明的,并被视为由type字段指定的更高级别协议处理的独立块。

本文档描述TLS 1.3,它使用版本0x0304。此版本值是历史的,源自TLS 1.0使用0x0301和SSL 3.0使用0x0300。为了最大化向后兼容性,包含初始ClientHello的记录应该 (SHOULD) 具有版本0x0301(反映TLS 1.0),包含第二个ClientHello或ServerHello的记录必须 (MUST) 具有版本0x0303(反映TLS 1.2)。在协商TLS的先前版本时,端点遵循附录D中提供的过程和要求。

当记录保护尚未启用时,TLSPlaintext结构直接写入线路。一旦记录保护开始,TLSPlaintext记录将受到保护并按照以下部分中的描述发送。请注意,应用数据记录不得 (MUST NOT) 未受保护地写入线路(有关详细信息,请参见第2节)。

5.2. Record Payload Protection (记录有效载荷保护)

记录保护函数将TLSPlaintext结构转换为TLSCiphertext结构。解保护函数反转该过程。在TLS 1.3中,与TLS的先前版本相反,所有密码都建模为"关联数据的认证加密" (AEAD) [RFC5116]。AEAD函数提供统一的加密和身份验证操作,它将明文转换为经过身份验证的密文,然后再转换回来。每个加密记录由明文头后跟加密主体组成,加密主体本身包含类型和可选填充。

struct {
opaque content[TLSPlaintext.length];
ContentType type;
uint8 zeros[length_of_padding];
} TLSInnerPlaintext;

struct {
ContentType opaque_type = application_data; /* 23 */
ProtocolVersion legacy_record_version = 0x0303; /* TLS v1.2 */
uint16 length;
opaque encrypted_record[TLSCiphertext.length];
} TLSCiphertext;

content: TLSPlaintext.fragment值,包含握手或警报消息的字节编码,或应用程序要发送的数据的原始字节。

type: 包含记录内容类型的TLSPlaintext.type值。

zeros: 任意长度的零值字节运行可能 (MAY) 在type字段之后出现在明文中。这为发送者提供了机会,只要总数保持在记录大小限制内,就可以将任何TLS记录填充选定的数量。有关更多详细信息,请参见第5.4节。

opaque_type: TLSCiphertext记录的外部opaque_type字段始终设置为值23 (application_data),以实现与习惯于解析TLS先前版本的中间盒的向外兼容性。记录的实际内容类型在解密后在TLSInnerPlaintext.type中找到。

legacy_record_version: legacy_record_version字段始终为0x0303。在TLS 1.3协商之后才生成TLS 1.3 TLSCiphertext,因此不存在可能接收其他值的历史兼容性问题。请注意,握手协议(包括ClientHello和ServerHello消息)对协议版本进行身份验证,因此此值是冗余的。

length: 以下TLSCiphertext.encrypted_record的长度(以字节为单位),它是内容和填充的长度之和,加上内部内容类型的一个字节,加上AEAD算法添加的任何扩展。长度不得 (MUST NOT) 超过2^14 + 256字节。接收超过此长度的记录的端点必须 (MUST) 使用"record_overflow"警报终止连接。

encrypted_record: 序列化TLSInnerPlaintext结构的AEAD加密形式。

AEAD算法将单个密钥、随机数、明文和要包含在身份验证检查中的"附加数据"作为输入,如[RFC5116]的第2.1节所述。密钥是client_write_key或server_write_key,随机数从序列号和client_write_iv或server_write_iv派生(参见第5.3节),附加数据输入是记录头。

即:

additional_data = TLSCiphertext.opaque_type ||
TLSCiphertext.legacy_record_version ||
TLSCiphertext.length

AEAD算法的明文输入是编码的TLSInnerPlaintext结构。流量密钥的派生在第7.3节中定义。

AEAD输出由AEAD加密操作的密文输出组成。由于包含TLSInnerPlaintext.type和发送者提供的任何填充,明文的长度大于相应的TLSPlaintext.length。AEAD输出的长度通常大于明文,但大的量随AEAD算法而变化。由于密码可能包含填充,因此开销量可能随明文的不同长度而变化。符号上:

AEADEncrypted =
AEAD-Encrypt(write_key, nonce, additional_data, plaintext)

TLSCiphertext的encrypted_record字段设置为AEADEncrypted。

为了解密和验证,密码将密钥、随机数、附加数据和AEADEncrypted值作为输入。输出是明文或指示解密失败的错误。没有单独的完整性检查。符号上:

plaintext of encrypted_record =
AEAD-Decrypt(peer_write_key, nonce,
additional_data, AEADEncrypted)

如果解密失败,接收者必须 (MUST) 使用"bad_record_mac"警报终止连接。

TLS 1.3中使用的AEAD算法不得 (MUST NOT) 产生大于255个八位字节的扩展。从其对等体接收TLSCiphertext.length大于2^14 + 256八位字节的记录的端点必须 (MUST) 使用"record_overflow"警报终止连接。此限制源自2^14八位字节的最大TLSInnerPlaintext长度 + 1个八位字节的ContentType + 255个八位字节的最大AEAD扩展。

5.3. Per-Record Nonce (每记录随机数)

为读取和写入记录分别维护一个64位序列号。在读取或写入每个记录后,相应的序列号递增1。每个序列号在连接开始时和每次更改密钥时设置为零;在特定流量密钥下传输的第一个记录必须 (MUST) 使用序列号0。

因为序列号的大小是64位,所以它们不应该回绕。如果TLS实现需要回绕序列号,它必须 (MUST) 重新密钥化(第4.6.3节)或终止连接。

每个AEAD算法将指定每记录随机数的可能长度范围,从N_MIN字节到N_MAX字节的输入[RFC5116]。TLS每记录随机数 (iv_length) 的长度设置为8字节和AEAD算法的N_MIN中的较大者(参见[RFC5116],第4节)。N_MAX小于8字节的AEAD算法不得 (MUST NOT) 与TLS一起使用。AEAD构造的每记录随机数形成如下:

  1. 64位记录序列号以网络字节顺序编码,并在左侧用零填充到iv_length。

  2. 填充的序列号与静态client_write_iv或server_write_iv(取决于角色)进行XOR。

所得数量(长度为iv_length)用作每记录随机数。

注意:这与TLS 1.2中的构造不同,TLS 1.2指定了部分显式随机数。

5.4. Record Padding (记录填充)

所有加密的TLS记录都可以 (MAY) 填充以膨胀TLSCiphertext的大小。这允许发送者向观察者隐藏流量的大小。

在生成TLSCiphertext记录时,实现可以 (MAY) 选择填充。未填充的记录只是填充长度为零的记录。填充是在加密之前附加到ContentType字段的零值字节字符串。实现必须 (MUST) 在加密之前将填充八位字节设置为全零。

如果发送者希望,应用数据记录可能 (MAY) 包含零长度的TLSInnerPlaintext.content。这允许在存在或缺少活动可能敏感的上下文中生成看似合理大小的掩护流量。实现不得 (MUST NOT) 发送具有零长度TLSInnerPlaintext.content的握手和警报记录;如果接收到这样的消息,接收实现必须 (MUST) 使用"unexpected_message"警报终止连接。

填充由记录保护机制自动验证;在成功解密TLSCiphertext.encrypted_record后,接收实现从末尾向开始扫描字段,直到找到非零八位字节。此非零八位字节是消息的内容类型。选择此填充方案是因为它允许任何加密的TLS记录填充任意大小(从零到TLS记录大小限制),而无需引入新的内容类型。该设计还强制执行全零填充八位字节,这允许快速检测填充错误。

实现必须 (MUST) 将其扫描限制为从AEAD解密返回的明文。如果接收实现在明文中未找到非零八位字节,它必须 (MUST) 使用"unexpected_message"警报终止连接。

填充的存在不会改变整体记录大小限制:完整编码的TLSInnerPlaintext不得 (MUST NOT) 超过2^14 + 1个八位字节。如果最大片段长度减少 - 例如,通过[RFC8449]的record_size_limit扩展 - 则减少的限制适用于完整明文,包括内容类型和填充。

选择填充策略来建议何时以及填充多少是一个复杂的主题,超出了本规范的范围。如果TLS之上的应用层协议有自己的填充,则在应用层内填充应用数据TLS记录可能更可取。但是,加密的握手或警报记录的填充仍必须在TLS层处理。以后的文档可能定义填充选择算法或通过TLS扩展或其他某种方式定义填充策略请求机制。

5.5. Limits on Key Usage (密钥使用限制)

在给定的一组密钥下可以安全加密的明文量存在密码学限制。[AEAD-LIMITS]在假设底层原语(AES或ChaCha20)没有弱点的情况下提供了这些限制的分析。实现应该 (SHOULD) 在达到这些限制之前按照第4.6.3节中的描述执行密钥更新。

对于AES-GCM,在给定连接上可以加密多达2^24.5个全尺寸记录(约2400万),同时保持约2^-57的认证加密 (AE) 安全裕度。对于ChaCha20/Poly1305,记录序列号将在达到安全限制之前回绕。


🎯 第5章完成度: 100%

已完成:

  • ✅ 5.1 Record Layer
  • ✅ 5.2 Record Payload Protection
  • ✅ 5.3 Per-Record Nonce
  • ✅ 5.4 Record Padding
  • ✅ 5.5 Limits on Key Usage