跳到主要内容

6. TLS 记录协议

TLS 记录协议用于封装正在传输的各种更高级别协议。它从上层接收任意大小的不透明数据,对数据进行分段(或重组,在输入端)、压缩、添加 MAC 并加密,然后传输结果。接收到的数据被解密、验证、解压并重新组装,然后传递给上层。

当使用 TLS 握手协议协商安全性时,记录层会接收对连接安全参数的更改以及来自上层协议的对内容类型变化的通知。这意味着在记录层之上,存在四种协议:握手协议、alert 协议、change cipher spec 协议和应用数据协议。

6.1. 连接状态​

连接状态指定了处于活动状态的压缩、加密和 MAC 算法。每个连接端点(客户端和服务器)在读取和写入两个方向上各有一个当前操作状态。当客户端和服务器完成握手时,它们会实例化写入状态(将由另一方读取);当另一方发送 change cipher spec 时,读取状态被更新。

连接状态由以下元素组成:

  • 压缩算法
  • 加密算法
  • MAC 算法
  • 用于读取或写入记录的密钥

这些参数由 TLS 握手协议协商。空状态(null state)表示未定义压缩、加密或 MAC 算法。在协商密码规范之前,TLS 记录协议在 null 状态下运行;此时所有记录均使用 null 压缩、null 加密和 null MAC 发送。

6.2. 记录层​

记录层接收要传输的记录,并处理当前连接状态的压缩、MAC 和加密。

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;

fragment 的最大长度为 2^14 字节(16384 字节)。length 不得超过 2^14 + 2048 字节。type 指定了上层协议正在传输数据的类型,本备忘录定义了四种内容类型:change_cipher_spec、alert、handshake 和 application_data。

6.2.1. 分段​

每个上层消息如果超过记录层的最大长度,就必须进行分段。反之,小于最大长度的数据也可以分段为多个记录。但请注意,客户端和服务器在接收到 change cipher spec 消息后,必须在尝试发送后续数据之前刷新记录层。

6.2.2. 记录压缩与解压​

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

支持所有符合规范的实现都必须包含 compression_method = null 的支持。与早期 TLS 版本一样,TLS 1.2 不要求实现任何压缩算法。null 压缩表示不执行压缩。压缩必须是无损的,且对长度的增加不得超过 1024 字节。如果解压函数遇到 TLSCompressed.fragment,且解压后长度超过 2^14 字节,则会生成致命的 decompression_failure alert。

6.2.3. 记录有效载荷保护​

TLSCompressed 结构包含需要保护的数据。TLSCompressed.fragment 的内容经完整性保护并(可选地)加密,生成 TLSCiphertext.fragment。

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

cipher_type 和 cipher 值由 SecurityParameters 确定。当前定义的 cipher_type 有 stream、block 和 aead。

6.2.3.1. 空或标准流加密​

对于流密码(包括 null 加密),加密通过将流密码的密钥流与 TLSCompressed.fragment 进行异或运算来转换 TLSCompressed.fragment,从而生成 TLSCiphertext.fragment。

stream-ciphered struct {
opaque content[TLSCompressed.length];
opaque MAC[SecurityParameters.mac_length];
} GenericStreamCipher;

注意,MAC 是在加密之前计算的。该 MAC 在 TLSCompressed 结构上计算,定义如下:

MAC(MAC_write_key, seq_num + TLSCompressed.type +
TLSCompressed.version + TLSCompressed.length +
TLSCompressed.fragment);

其中,seq_num 是此记录的序号(每个连接状态一个 64 位序列号);version 是正在使用的协议版本。

null 密码不加密任何内容。在 null 加密状态下,通过 TLS 记录层发送的数据 MAC 但未被加密。请注意,null 加密不应用于保护敏感数据;它仅用于调试以及在没有合适密码可用的情况下使用。

6.2.3.2. CBC 块加密​

对于分组密码(如 AES、3DES 等),加密通过将分组密码的密钥流与 TLSCompressed.fragment 进行异或运算来转换 TLSCompressed.fragment,从而生成 TLSCiphertext.fragment。null 加密不在此处理。

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

padding 的长度可以是 0 到 255 之间的任意整数;填充后的 GenericBlockCipher 总长度必须是密码块长度的整数倍(例如对于 AES 为 16 字节)。长度不满足此条件的记录必须被拒绝,并生成致命的 bad_record_mac alert。注意:使用 SSLv3 兼容实现时,填充必须是最小长度;否则,可能受到基于填充长度的攻击(如 [CBCATT])。padding 字节的值是 padding_length 字段的值(例如,如果 padding_length 为 11,则每个填充字节都为 11)。

padding_length 是最后一个字节,其值应为填充字节总数减一(例如 11 个字节的填充为 11)。此计算会包含 padding_length 字段本身(其值为 0 表示无填充量)。当 padding_length 大于 255 时,会发送致命的 bad_record_mac alert。处理完填充后,MAC 会与计算后的 MAC 进行比较。

6.2.3.3. AEAD 加密​

对于 AEAD 密码(如 AES-GCM、AES-CCM),加密将 TLSCompressed.fragment 与 nonce 进行异或运算(使用 AEAD 密码),从而生成 TLSCiphertext.fragment。

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

nonce 的计算方式如下:nonce = nonce_explicit XOR nonce_implicit。其中,nonce_explicit 由记录层提供,nonce_implicit 是 client_write_IV(或 server_write_IV),在握手期间协商。content 是经过认证的加密数据,附加数据(additional_data)的计算如下:

additional_data = seq_num + TLSCompressed.type +
TLSCompressed.version + TLSCompressed.length;

nonce_explicit 的长度由 SecurityParameters.record_iv_length 确定,且该长度不得超过 2^14 字节。seq_num 是记录序号。type、version 和 length 字段是记录的元数据。AEAD 密码的输出长度等于输入长度加上身份验证标签(tag)长度(例如,对于 AES-GCM 为 16 字节)。

注意:本译文由 AI 翻译,可能包含错误。请参考 RFC 5246 官方英文原文进行核对。