作为构建在TCP之上应用层之下的协议,TLS是独立开来的协议,任何应用层协议都可以直接引入使用它,其最重要的作用就是将应用层协议,如FTP,HTTP或SMTP等加密处理后,再传递给下层的TCP协议来保证数据传输的可靠性。TLS将来自高层的协议数据进行封装,过程中分有两层结构,握手层和记录层,握手层在上,记录层在下,所以握手层更接近应用层,记录层更接近传输层TCP。记录层协议的作用大致有,将上层协议封装,将数据分块,数据压缩(通常不使用),最重要的数据加密解密,验证,这篇日志就来总结下TLS协议的记录层。
记录层消息头
记录层协议将上层的握手层一条或多条子消息,添加上自己的消息头,最终封装成一条消息,再往下传递给TCP处理,那么记录层的消息头里都有些什么呢?来看看:

记录层协议的消息头一共5个字节组成,分为三部分,首先是消息类型,占一个字节,消息类型有四种,对应的是上一层握手层的四种自协议:Change Cipher Spec Protocol密码切换协议、Alert Protocol警告协议,TLS Handshaking Protocols握手协议和Application Data Protocol应用层协议,对应的十六进制编号按顺序分别为0x14到0x17。接着是两个字节的版本号,版本号标志的是TLS协议的版本号,TLS1.0—TLS1.3,最新的版本是2018年更新的TLS1.3。再往下是两个字节的消息长度,故名思意是标识本条消息的长度,但是注意,不是指记录层消息头的长度,消息头长度是固定5个字节的(当然未来可能会扩展消息类型的字节长度),记录层消息头里的消息长度标识的是记录层将上层协议封装成一条消息后的消息长度。
连接状态
HTTPS里客户端和服务器端每次建立连接,也就是建立一个session会话时,都会有一个连接状态,一个TLS连接状态标识的就是一个记录层的环境,连接状态的不同标识着LTS协议的不同过程,一共有四种:
pending read states(待读状态)
pending write states(待写状态)
current read states(可读状态)
current write states(可写状态)
加密参数
在客户端和服务器端一开始建立连接时,双方都处于待读状态和待写状态,等待加密参数准备好(加密参数填充到密码套件中),加密参数中指定了加密函数(如aes算法),加密模式(流密码,块密码或者aead),还有密钥长度,MAC消息验证码算法,主密钥等。这些加密参数都由记录层的上一层握手层协议提供,TLS记录层得到加密参数后,客户端和服务器端就可以改变连接状态,使用加密参数的值进行加密和解密操作。客户端和服务器端互相发送Change Cipher Spec Protocol密码切换协议消息给对方,提示可以进行连接状态的切换,随后双方都切换为可读状态和可写状态,使用加密参数进行加密解密。
记录层处理过程
连接状态完成后,TLS记录层协议的主要工作还没完,前面说到,TLS记录层会将上层的消息添加消息头,封装处理后再交给下层TCP传输层,除了添加消息头外,记录层还做了数据分块,数据压缩和数据加密的工作,总的来说,TLS记录层协议的主要工作就是这四部分。

数据分块
当上层的握手层协议消息进入TLS记录层后,首先进行数据分块,分块成大小不超过2^14字节的TLSPlaintext数据结构:
//TLS协议协商得到的版本号
struct {
uint8 major;
uint8 minor;
} ProtocolVersion;
//TLS上层协议的四个子协议
enum {
change_cipher_spec(20),
alert(21),
handshake(22),
application_data(23),
} ContentType;
struct {
ContentType type;
ProtocolVersion version;
uint16 length;
opaque fragment[TLSPlaintext.length];
} TLSPlaintext;
从TLSPlaintext结构中可以看到,ContentType标示的是上层的协议的类型,ProtocolVersion标识的是TLS协议的版本号,length标识的是数据分块后的块大小,长度不超过2^14字节,最后一个fragment是上层协议消息,此时还没有进行机密性保护,属于明文处理。
数据压缩
数据压缩的数据结构TLSCompressed如下:
struct {
ContentType type;
ProtocolVersion version;
uint16 length;
opaque fragment[TLSCompressed.length];
} TLSCompressed;
数据压缩算法将TLSPlaintext数据结构转换成TLSCompressed结构,如果不选择进行数据压缩,那么TLSPlaintext结构就不会被转换成TLSCompressed结构。在TLS1.2版本协议中,压缩算法一般不会被使用,因为安全问题,在TLS1.3版本里直接将数据压缩操作给删除了,增加了一个数据填充操作,数据发送方可以选择在数据加密前为消息填充一段零值字节,之后再进行加密,目的是为了隐藏真实消息长度,如果选择填充,那么TLSPlaintext结构会被转换成TLSInnerPlaintext结构。
加密
数据加密部分,消息进行压缩(通常不使用),填充之后,接下来进行加密,上述的TLSPlaintext,TLSCompressed或者TLSInnerPlaintex结构会被转换为TLSCiphertext结构:
struct {
ContentType type;
ProtocolVersion version;
uint16 length;
select (SecurityParamters.cipher_type) {
case stream: GenericStreamCipher;
case block: GenericBlockCipher;
case aead: GenericAEADCipher;
} fragment;
} TLSCiphertext;
前面三个属性不用多加解释了,fragment是最终加密后的数据,加密方式有三种,流密码,块密码和AEAD三种。加密后的消息末尾还要加上MAC值,里面包含了一个序列号,序列号十分重要,用来检测消息是否有丢失或者重复等,具体的做法是:
- 初始化时客户端和服务器端都各自维护一个序列号,序列号初值为0。
- 客户端与服务器端建立连接,客户端生成client_recv和client_send两个变量,分别用来记录客户端处接收的数据块总量和发送数据块总量,服务器端同理。
- 客户端发送一个消息后,client_send加一,服务器端接收到一个客户端消息后,对MAC值进行校验,记录serve_recv。如果客户端和服务器端之间消息发送没有出现丢失或者重复的错误,那么client_send的值就会等于server_recv的值,server_send的值等于client_recv的值。
添加消息头
最后,经过加密处理的LSCiphertext结构添加记录层消息头,就可以传递给下层TCP传输层了,消息头的内容,上面已经总结,包含了一个字节的消息类型,两个字节的版本号和消息长度。
本文详细介绍了TLS协议中的记录层,包括记录层消息头的结构、连接状态的演变、加密参数的设定以及记录层处理过程,如数据分块、数据压缩(TLS1.3中已删除)、加密和添加消息头。记录层在确保应用层协议(如HTTP、FTP等)数据的加密和可靠性方面起着关键作用。
1991

被折叠的 条评论
为什么被折叠?



