Skip to main content

6. The TLS Record Protocol (TLS记录协议)

TLS记录协议 (TLS Record Protocol) 是一个分层协议. 在每一层, 消息可以包括长度, 描述和内容的字段. 记录协议接收要传输的消息, 将数据分段为可管理的块, 可选地压缩数据, 应用MAC, 加密, 然后传输结果. 接收到的数据被解密, 验证, 解压缩, 重新组装, 然后交付给更高级别的客户端.

本文档描述了使用记录协议的四个协议: 握手协议 (handshake protocol), 警报协议 (alert protocol), 更改密码规范协议 (change cipher spec protocol), 以及应用数据协议 (application data protocol). 为了允许TLS协议的扩展, 记录协议可以支持其他记录内容类型. 新的记录内容类型值由IANA在TLS内容类型注册表中分配, 如第12节所述.

实现禁止 (MUST NOT) 发送本文档中未定义的记录类型, 除非由某种扩展协商. 如果TLS实现接收到意外的记录类型, 它必须 (MUST) 发送unexpected_message警报.

任何设计用于在TLS上使用的协议都必须仔细设计以应对针对它的所有可能攻击. 实际上, 这意味着协议设计者必须意识到TLS提供和不提供哪些安全属性, 并且不能安全地依赖后者.

特别要注意的是, 记录的类型和长度不受加密保护. 如果此信息本身是敏感的, 应用程序设计者可能希望采取措施 (填充, 掩盖流量) 来最小化信息泄漏.

6.1. Connection States (连接状态)

TLS连接状态 (TLS connection state) 是TLS记录协议的操作环境. 它指定压缩算法, 加密算法和MAC算法. 此外, 这些算法的参数是已知的: 连接在读和写两个方向上的MAC密钥和批量加密密钥 (bulk encryption keys). 逻辑上, 始终有四个未完成的连接状态: 当前读和写状态, 以及待定读和写状态. 所有记录都在当前读和写状态下处理. 待定状态的安全参数可以由TLS握手协议设置, ChangeCipherSpec可以选择性地使任何一个待定状态成为当前状态, 在这种情况下, 相应的当前状态被丢弃并替换为待定状态; 然后待定状态被重新初始化为空状态. 使尚未用安全参数初始化的状态成为当前状态是非法的. 初始当前状态始终指定不使用加密, 压缩或MAC.

通过提供以下值来设置TLS连接读和写状态的安全参数:

connection end (连接端)

  • 此实体在此连接中被视为"客户端"还是"服务器".

PRF algorithm (PRF算法)

  • 用于从主密钥生成密钥的算法 (参见第5节和第6.3节).

bulk encryption algorithm (批量加密算法)

  • 用于批量加密的算法. 此规范包括此算法的密钥大小, 它是块密码, 流密码还是AEAD密码, 密码的块大小 (如果适用), 以及显式和隐式初始化向量 (或nonces) 的长度.

MAC algorithm (MAC算法)

  • 用于消息认证的算法. 此规范包括MAC算法返回的值的大小.

compression algorithm (压缩算法)

  • 用于数据压缩的算法. 此规范必须包括算法执行压缩所需的所有信息.

master secret (主密钥)

  • 连接中两个对等方之间共享的48字节秘密.

client random (客户端随机数)

  • 客户端提供的32字节值.

server random (服务器随机数)

  • 服务器提供的32字节值.

这些参数在表示语言中定义为:

enum { server, client } ConnectionEnd;

enum { tls_prf_sha256 } PRFAlgorithm;

enum { null, rc4, 3des, aes }
BulkCipherAlgorithm;

enum { stream, block, aead } CipherType;

enum { null, hmac_md5, hmac_sha1, hmac_sha256,
hmac_sha384, hmac_sha512} MACAlgorithm;

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

/* The algorithms specified in CompressionMethod, PRFAlgorithm,
BulkCipherAlgorithm, and MACAlgorithm may be added to. */

struct {
ConnectionEnd entity;
PRFAlgorithm prf_algorithm;
BulkCipherAlgorithm bulk_cipher_algorithm;
CipherType cipher_type;
uint8 enc_key_length;
uint8 block_length;
uint8 fixed_iv_length;
uint8 record_iv_length;
MACAlgorithm mac_algorithm;
uint8 mac_length;
uint8 mac_key_length;
CompressionMethod compression_algorithm;
opaque master_secret[48];
opaque client_random[32];
opaque server_random[32];
} SecurityParameters;

记录层将使用安全参数生成以下六个项 (某些项并非所有密码都需要, 因此为空):

  • client write MAC key (客户端写MAC密钥)
  • server write MAC key (服务器写MAC密钥)
  • client write encryption key (客户端写加密密钥)
  • server write encryption key (服务器写加密密钥)
  • client write IV (客户端写IV)
  • server write IV (服务器写IV)

服务器在接收和处理记录时使用客户端写参数, 反之亦然. 从安全参数生成这些项的算法在第6.3节中描述.

一旦设置了安全参数并生成了密钥, 就可以通过使它们成为当前状态来实例化连接状态. 这些当前状态必须 (MUST) 为每个处理的记录更新. 每个连接状态包括以下元素:

compression state (压缩状态)

  • 压缩算法的当前状态.

cipher state (密码状态)

  • 加密算法的当前状态. 这将包含该连接的调度密钥. 对于流密码, 这还将包含允许流继续加密或解密数据所需的任何状态信息.

MAC key (MAC密钥)

  • 此连接的MAC密钥, 如上所述生成.

sequence number (序列号)

  • 每个连接状态都包含一个序列号, 读和写状态分别维护. 只要连接状态成为活动状态, 序列号必须 (MUST) 设置为零. 序列号的类型是uint64, 不能超过2^64-1. 序列号不循环. 如果TLS实现需要循环序列号, 它必须重新协商. 序列号在每个记录后递增: 具体来说, 在特定连接状态下传输的第一个记录必须 (MUST) 使用序列号0.

6.2. Record Layer (记录层)

TLS记录层从更高层接收任意大小的非空块中的未解释数据.

6.2.1. Fragmentation (分段)

记录层将信息块分段为TLSPlaintext记录, 以2^14字节或更少的块携带数据. 客户端消息边界不在记录层中保留 (即, 相同ContentType的多个客户端消息可以 (MAY) 合并到单个TLSPlaintext记录中, 或者单个消息可以 (MAY) 跨多个记录分段).

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;

type

  • 用于处理封闭片段的更高级别协议.

version

  • 所使用协议的版本. 本文档描述TLS 1.2版, 它使用版本 3. 版本值3.3是历史原因, 源于TLS 1.0使用 1. (参见附录A.1.) 请注意, 支持多个TLS版本的客户端在收到ServerHello之前可能不知道将使用哪个版本. 有关ClientHello应使用哪个记录层版本号的讨论, 请参见附录E.

length

  • 以下TLSPlaintext.fragment的长度 (以字节为单位). 长度禁止 (MUST NOT) 超过2^14.

fragment

  • 应用数据. 此数据是透明的, 并被视为由type字段指定的更高级别协议要处理的独立块.

实现禁止 (MUST NOT) 发送握手, 警报或ChangeCipherSpec内容类型的零长度片段. 应用数据的零长度片段可以 (MAY) 发送, 因为它们可能作为流量分析对策有用.

注意: 不同TLS记录层内容类型的数据可以 (MAY) 交错. 应用数据通常以较低优先级用于传输, 相对于其他内容类型. 但是, 记录不能跨越ChangeCipherSpec消息边界.

6.2.2. Record Compression and Decompression (记录压缩和解压缩)

所有记录在压缩之前使用在当前会话状态中定义的压缩算法进行压缩. 始终存在一个活动的压缩算法; 但是, 初始时它被定义为CompressionMethod.null. 压缩算法将TLSPlaintext结构转换为TLSCompressed结构. 压缩必须是无损的, 并且不能将内容长度增加超过1024字节. 如果解压缩函数遇到TLSCompressed.fragment, 解压缩后将超过2^14字节, 它必须 (MUST) 报告致命的decompression_failure警报.

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

length

  • 以下TLSCompressed.fragment的长度 (以字节为单位). 长度禁止 (MUST NOT) 超过2^14 + 1024.

fragment

  • TLSPlaintext.fragment的压缩形式.

注意: CompressionMethod.null操作是恒等操作; 不进行字段更改.

实现注意: 解压缩函数负责确保消息不会导致内部缓冲区溢出.

6.2.3. Record Payload Protection (记录载荷保护)

加密和MAC函数将TLSCompressed结构转换为TLSCiphertext. 使用当前连接状态 (参见第6.1节) 中定义的写连接状态对记录进行解密和验证. 解密和验证的详细信息取决于所使用的密码类型.

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

type

  • 类型字段与TLSCompressed.type相同.

version

  • 版本字段与TLSCompressed.version相同.

length

  • 以下TLSCiphertext.fragment的长度 (以字节为单位). 长度禁止 (MUST NOT) 超过2^14 + 2048.

fragment

  • TLSCompressed.fragment的加密形式, 带有MAC.

6.2.3.1. Null or Standard Stream Cipher (空密码或标准流密码)

流密码 (包括BulkCipherAlgorithm.null; 参见附录A.6) 将TLSCompressed.fragment结构转换为流TLSCiphertext.fragment结构.

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

MAC是通过以下方式生成的:

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

其中 "+" 表示连接.

seq_num

  • 此记录的序列号.

MAC

  • 在SecurityParameters.mac_algorithm中指定的MAC算法.

请注意, MAC是在压缩之后但在加密之前计算的. 流密码加密整个块, 包括MAC.

6.2.3.2. CBC Block Cipher (CBC分组密码)

对于分组密码 (如3DES或AES), 加密和MAC功能将TLSCompressed.fragment转换为TLSCiphertext.fragment的块密码形式.

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;

MAC的生成方式与流密码相同.

IV

  • 初始化向量 (IV) 应该随机选择, 并且对于每个记录必须是不可预测的. 请注意, 在TLS 1.1及更高版本中, IV字段不需要对CBC分组密码中的每个记录重新生成 (但这是允许的). 取而代之的是, 对于给定的密钥, IV对于每个记录都是唯一的. 特别是, 记录的序列号可以用作IV, 或者记录的一部分可以用作IV. TLS 1.0及更低版本中的隐式IV已被删除.

padding

  • 填充, 对于分组密码是必需的, 以强制明文长度为密码块长度的偶数倍. 填充可以是从零字节到255字节的任意长度, 只要它导致TLSCiphertext.length为密码块长度的偶数倍即可. 填充的长度可以大于达到块大小的倍数所需的最小值. 例如, 如果明文 (包括MAC) 长度为79字节且块大小为8字节, 则填充长度可以是1到7字节之间 (79+1=80是8的倍数). 它可以是9字节 (79+9=88), 17字节, 等等, 直到255字节. 这允许发送者通过添加可变数量的填充来隐藏明文的精确长度.

padding_length

  • 填充长度必须使填充后的总长度为密码块大小的偶数倍. 合法值范围从零到255 (包括). 此长度指定填充字段的长度, 不包括padding_length字段本身.

填充的内容对于安全性无关紧要, 实现可以选择填充任何值, 只要保持填充长度正确即可.

注意: 对于块密码, 可能需要封装明文 (包括MAC和padding) 超过2^14字节. 例如, 如果将2^14字节明文应用于块密码, 则可能需要最多255字节的填充, 从而使总大小为2^14 + 256字节. 由于密文始终至少与输入明文一样大, 因此TLSCiphertext记录的长度可能会超过2^14字节. 这不会引起问题, 因为TLS实现不会解密从对等方接收的记录, 除非首先验证该记录不超过2^14字节 (加上填充).

6.2.3.3. AEAD Ciphers (AEAD密码)

对于AEAD [AEAD] 密码 (如CCM或GCM), AEAD函数将TLSCompressed.fragment结构转换为AEAD TLSCiphertext.fragment结构.

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

AEAD密码接受作为输入的单个密钥, 一个nonce, 一个明文, 以及要包含在认证检查中的"附加数据", 如 [AEAD] 第2.1节所述. 密钥是client_write_key或server_write_key. 不使用MAC密钥.

每个AEAD密码套件必须指定nonce的构造方式, 并且什么是GenericAEADCipher.nonce_explicit的长度. 在许多情况下, 建议使用部分随机和部分使用序列号的方法. 一个示例是附录C中的密码套件.

6.3. Key Calculation (密钥计算)

记录协议需要一个算法从握手协议提供的安全参数生成当前连接状态所需的密钥.

主密钥 (master secret) 被扩展为安全密钥材料序列. 此扩展是通过调用PRF完成的:

key_block = PRF(SecurityParameters.master_secret,
"key expansion",
SecurityParameters.server_random +
SecurityParameters.client_random);

直到生成足够的输出. 然后, key_block按以下方式分割:

client_write_MAC_key[SecurityParameters.mac_key_length]
server_write_MAC_key[SecurityParameters.mac_key_length]
client_write_key[SecurityParameters.enc_key_length]
server_write_key[SecurityParameters.enc_key_length]
client_write_IV[SecurityParameters.fixed_iv_length]
server_write_IV[SecurityParameters.fixed_iv_length]

当前定义的密码套件使用最多AES_256_CBC_SHA256的SecurityParameters.enc_key_length=32的密码. 因此, 91字节的key_block足够满足任何受支持的密码套件.

未使用的值为空. 某些AEAD密码可能会额外定义如何处理由key_block产生的密钥.

当为导出密钥 (exportable cipher) 生成密钥和MAC密钥时, 主密钥用于生成所需数量的密钥材料, 然后进行分割. 导出加密不应在新实现中使用.