注:本文省略了部分开发协议才涉及到的内容,如字段类型的定义以及字段长度的运算,主要聚焦理解tls协议的运作方式,用于问题定位
tls协议包含2层协议:TLS Record 协议和TLS Handshake协议,底层采用可靠传输协议(如TCP),TLS Record协议通过如下方式实现数据的安全传输:
链路是私有的,使用对称加密方式对应用数据进行加密。对每条链路来说,对称加密使用的key是各自独立的(通过TLS Handshake协议协商得到)。Record协议也可以用于非加密场景;
链路是可靠的。使用MAC来对消息完整性进行校验,MAC使用哈希函数(如SHA-1)进行运算。Record 协议可以不使用MAC,但通常仅限于使用Record作为底层协议来传输协商使用的安全参数。
TLS Record 协议用于封装多种上层协议,如Handshake协议,用于在传输/接收应用数据前进行相互认证,并协商出加密算法以及使用的密钥。Handshake使用如下三种方式提供数据的安全传输:
对端身份可以通过非对称算法或公钥,加密(如RSA,DSA)等进行认证。该认证是可选的,但通常要求至少通过一种认证方式对对端进行认证;
协商的共享密钥的过程是安全的:窃听者无法获取协商的密钥;
协商是可靠的:攻击者无法在不被链路探测到的情况下修改协商报文。
使用TLS的好处是它与应用层协议是相互独立的,上层协议运行在TLS协议之上。TLS协议没有指出如何添加协议来对链路进行安全加固。如何初始化TLS握手以及如何使用认证的证书,这些交由TLS之上的协议设计者来实现。
5. HMAC and the Pseudorandom Function
TLS使用MAC来保证消息的合法性。本协议使用的cipher suites使用了HMAC,它的实现基于hash函数,其他cipher suites可能使用其他形式的MAC。
除此之外,还需要一种在生成或校验key时将密钥扩展为数据块的方式。PRF(pseudorandom function)可以通过输入密钥,种子和认证的标签之后给出任意长度的输出。PRF基于HMAC。本文档所有cipher suites使用的的PRF均采用SHA-256的哈希函数。新的cipher suites必须明确规定PRF,并使用SHA-256或更健壮的hash函数。
cipher spec规定了如何使用PRF生成key material,块加密算法(AES null等)以及MAC算法(HMAC-SHA1)。
使用PRF首先需要定义一个扩展密钥的函数,P_hash(secret, data),该函数用于扩展密钥长度
代码语言:javascript复制P_hash(secret, seed) = HMAC_hash(secret, A(1) seed)
HMAC_hash(secret, A(2) seed)
HMAC_hash(secret, A(3) seed) ...
A()定义如下:
代码语言:javascript复制A(0) = seed
A(i) = HMAC_hash(secret, A(i-1))
P_hash可以迭代多次来生成所需要的数据。如使用p_SHA256来生成80字节的数据,则需要迭代3次(32bytes*3=96bytes)来生成96字节的数据,保留前面80字节,后16字节的数据会被丢弃。
使用P_hash生成PRF的方式如下:
代码语言:javascript复制PRF(secret, label, seed) = P_<hash>(secret, label seed)
label是ASCII字符串,如字符串"slithy toves"使用时应该为ASCII:
代码语言:javascript复制73 6C 69 74 68 79 20 74 6F 76 65 73
6. The TLS Record Protocol
TLS Record 协议是一个层级协议,每一层消息可能包括length,description和content字段。Record协议负责将需要传输的数据分布到管理的block中,可能会对数据进行压缩,在使用MAC和加密后传输结果。接收端接收到消息之后需要进行解密,校验和解压缩操作,然后传递到上层业务。当接收到未知的Record 类型后,必须发送unexpected_message alert。
本文档定义了4种使用record协议的协议,handshake协议,alert 协议,change cipher spec协议以及application data协议。
6.1. Connection States
TLS Record协议使用TLS connection state作为运行环境,connection state指定了压缩算法,加密算法以及MAC算法,且拥有MAC key和bulk encryption keys相关的参数。Record协议始终保持4个connection state:current read,current write以及pending read和pending write。所有的records都在current read和current write state下处理。pending state可以被TLS Handshake协议修改,而ChangeCipherSpec消息可以选择性地将pending state变为current state,此时pending state初始化为empty state。给未初始化的state配置参数并使之变为current state的方式是非法的。初始的current state没有使用加密,压缩以及MAC。
connection states用于跟踪加密状态。write key用来发送数据,read key用来接收数据。pending state用来保存新的加密key(可以由Handshake协议生成)以及初始向量,而current state则表示当前使用的key,当接受到ChangeCipherSpec消息时,会将pending state的key覆盖current state的key,这样就会使用更新的key进行数据的收发。当更多关于connection states的介绍可以参考:What are read state and write state in SSL connections
TLS Connection read 和write state的安全参数如下:
connection end:接入的实体是”client“还是”server“
PRF算法:该算法通过master secret生成keys
bulk encryption 算法:用于块加密
MAC 算法:用于消息认证
compression算法:用于数据压缩
master secret:链路两端共享48字节的密钥
client random:client提供的32字节的随机数
server random:server提供的32字节的随机数
Record层使用安全参数来生成如下6项内容:
server write MAC key
client write encryption key
server write encryption key
client write IV
server write IV
使用PRF生成如下的key_block,key_block最终分割为以上key
代码语言:javascript复制key_block = PRF(SecurityParameters.master_secret,"key expansion",SecurityParameters.server_random SecurityParameters.client_random);
server在接收和处理record的时候使用client write参数,反之亦然。
一旦设定安全参数并生成key之后,就可以通过将状态变为current state来实例化connection state。这些current state必须同时更新到每个被处理的record层。每个connection state包含如下元素:
compression state:current state的压缩算法
cipher state:current state的加密算法,包含了该connection需要调度的key
MAC key:connection的MAC key
sequence number:每个connection state都包含一个序列号,且分别由read和write state维护。当一个connection state变为active state时必须置为0。序列号会在每个record中递增。
6.2. Record Layer
6.2.1. Fragmentation
代码语言:javascript复制Record层将消息块分割为TLSPlaintext结构(长度<=2^14),相同ContentType的client消息可能合并到一个TLSPlaintext record中,一个client消息也可能分布到多个record中。
代码语言:javascript复制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:上层协议的类型,用于处理封装fragment
version:协议采用的版本,本文档值为{ 3, 3 }
length:TLSPlaintext.fragment的长度,不能大于2^14
fragment:应用数据。由上层协议处理的特定类型的数据块
不能发送0长度的Handshake fragments,alert或ChangeCipherSpec数据。但可以发送0长度的应用数据(可能用于链路探测)。在TLS Record协议中,应用数据处理的优先级很低,一般在上层协议(Handshake)处理完成后发送。
6.2.2. Record Compression and Decompression
所有的record都使用current state中定义的压缩算法进行压缩。初始的压缩算法为CompressionMethod.null(任何时候都有一个激活的压缩算法)。压缩算法将TLSPlaintext结构转换为TLSCompressed结构。当connection state变为激活态时,压缩算法会使用默认的state信息进行初始化。压缩算法不能造成数据丢失,且内容长度不能超过1024字节,当解压缩函数解压缩TLSCompressed.fragment时,如果数据大于2^14字节,必须返回解压缩失败的错误。
代码语言:javascript复制struct {
ContentType type; /* same as TLSPlaintext.type */
ProtocolVersion version;/* same as TLSPlaintext.version */
uint16 length;
opaque fragment[TLSCompressed.length];
} TLSCompressed;
length:TLSCompressed.fragment的长度,不能大于2^14 1024字节
fragment:压缩TLSPlaintext.fragment之后的数据类型,如下handshake protocol作为Record协议的数据部分
解压缩函数必须保证不能造成buffer溢出
实际使用中一般禁用压缩
6.2.3. Record Payload Protection
加密和MAC函数会将TLSCompressed结构转化为TLSCiphertext结构,解密函数则反转上述过程。record的MAC包含序列号,因此能够探测到重复,多余,缺失消息的场景。
TLSCiphertext中定义了流加密和块加密,以及AEAD加密方式。流加密和块加密的简单理解可以参考对称加密&加密模式
代码语言:javascript复制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的长度,不能超过2^14 1024
fragment:加密TLSCompressed.fragment后的类型(含MAC)
6.2.3.1. Null or Standard Stream Cipher
Stream ciphers将TLSCompressed.fragment结构转换为TLSCiphertext.fragment的stream结构,如下:
代码语言:javascript复制stream-ciphered struct {
opaque content[TLSCompressed.length];
opaque MAC[SecurityParameters.mac_length];
} GenericStreamCipher;
MAC的生成方式如下:
代码语言:javascript复制MAC(MAC_write_key, seq_num TLSCompressed.type TLSCompressed.version TLSCompressed.length TLSCompressed.fragment);
seq_num:record的序列号
MAC:SecurityParameters.mac_algorithm指定的MAC算法
MAC必须在被加密前计算出来,stream cipher用于加密整个数据块(含MAC)。如果cipher suite为TLS_NULL_WITH_NULL_NULL,加密方式则由操作类型组成(如数据没有被加密,MAC为0--即NULL的定义)。对于null和stream ciphers来说,TLSCiphertext.length = TLSCompressed.length SecurityParameters.mac_length
6.2.3.2. CBC Block Cipher
block cipher(如3DES或AES)使用加密和MAC函数将TLSCompressed.fragment结构转换为TLSCiphertext.fragment的block结构,如下:
代码语言:javascript复制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生成方式与stream cipher相同
IV:IV(Initialization Vector)为随机数且必须为不可预测。
padding:将plantext扩展到完整的(多个)cipher block的长度,长度最大可以为255字节。padding机制可以防止基于报文长度分析进行的攻击。接收端必须检验该padding,并在校验失败时返回bad_alert_mac_alert错误。
padding_lenght:padding length必须使GenericBlockCipher 结构的长度和多个cipher block的长度相同。该长度不包括padding_length本身。
CBC(Cipher Block Chaining)模式下必须知道所有record的明文内容,否则可能遭受CBCTT攻击。
6.2.3.3. AEAD Ciphers
AEAD cipher(如CCM或GCM)函数将TLSCompressed.fragment结构转换为TLSCiphertext.fragment的AEAD结构,如下:
代码语言:javascript复制struct {
opaque nonce_explicit[SecurityParameters.record_iv_length];
aead-ciphered struct {
opaque content[TLSCompressed.length];
};
} GenericAEADCipher;
AEAD使用key,nonce(随机数),plaintest和"additional data"作为输入进行身份认证。key可以是client_write_key或server_write_key,没有用到MAC key。paintext为TLSCompressed.fragment。额外的认证数据也被称为additional_data,定义如下:
代码语言:javascript复制additional_data = seq_num TLSCompressed.type TLSCompressed.version TLSCompressed.length
aead_output输出包括AEAD加密操作的密文,其长度通常大于TLSCompressed.length,但会随着AEAD cipher变化。由于ciphers可能会包含padding,数量可能会随着TLSCompressed.length的不同而不同。每个AEAD cipher的输出不能大于1024字节。
代码语言:javascript复制AEADEncrypted = AEAD-Encrypt(write_key, nonce, plaintext,additional_data)
为了加密和验证,AEAD cipher会采用key,nonce和"additional_data"以及AEADEncrypted值。输出为明文或错误信息,没有完整性校验。如果解密失败,则发出bad_record_mac alert
代码语言:javascript复制TLSCompressed.fragment = AEAD-Decrypt(write_key, nonce,AEADEncrypted,additional_data)
6.3. Key Calculation
Record协议使用算法结合handshake协议提供的安全参数来生成current state需要的keys。master secret被分割为client write MAC key,server write MAC key,client write encryption key和server write encryption key,这4个key按照字节顺序分割,未使用的值为空。一些AEAD ciphers会使用到client write IV和server write IV。
当keys和MAC keys生成后,master secret作为熵。此处使用PRF生成key
代码语言:javascript复制key_block = PRF(SecurityParameters.master_secret,
"key expansion",
SecurityParameters.server_random
SecurityParameters.client_random);
key_block划分如下:
代码语言:javascript复制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]
当前client_write_IV 和server_write_IV仅用于AEAD(3.2.1章节)
master secret算法如下:
代码语言:javascript复制master_secret = PRF(pre_master_secret, "master secret",
ClientHello.random ServerHello.random)
[0..47];
Record协议使用connection state来维护加密状态,它使用handshake协议来获取安全参数并生成相应的key,最后对数据进行加密传输。
7. The TLS Handshaking Protocols
TLS有3个子协议用于为record层提供安全参数,进行相互认证,初始化协商以及报告错误。
handshake用来协商session,包含以下项目:
session identifier:server端选择的随机序列号,用于标记激活的或是重用的session
peer certificates:对端的X509v3证书。
compression method:加密前使用的压缩算法
cipher spec:指定用于生成key的PRF,块加密算法(null,AES等)和MAC算法(如HMAC-SHA1)。
master secret:client和server的48字节的共享密钥
is resumable:用于标记该session是否能被重用
这些参数用来生成安全参数。可以通过Handshake协议的重用特性,使用相同的session来初始化链接。
7.1. Change Cipher Spec Protocol
change cipher spec协议主要用来发送改变加密策略的信号。该协议仅包含一个消息(使用current connection state压缩并加密)。消息内容包含一个字节的内容
代码语言:javascript复制struct {
enum { change_cipher_spec(1), (255) } type;
} ChangeCipherSpec;
client或server通过发送ChangeCipherSpec消息来通知对端后续records将使用新协商的CipherSpec和keys加密保护。接收端接收到该消息后,会通知Record层将read pending state中的内容拷贝到read current state中。在发送完该消息之后,发送端必须将write pending state中的内容拷贝到write active state中。
协商好的安全参数会保存在write pending state中,当一端需要使用新的加密算法时,会发送ChangeCipherSpec消息,并立即将write pending state中的内容覆盖到write current state中,接收端则修改read state的内容。
ChangeCipherSpec 在握手过程中且在两端都协商好安全参数后(Finished消息前)发送。
如果在数据传输过程中出现了重握手,双方仍然可以使用旧的CipherSpec。但是当收到ChangeCipherSpec消息时,必须使用新的CipherSpec。发送ChangeCipherSpec的一段并不知道对端是否已经计算出了新的keys,因此在接收端可能需要一小段时间来缓存接收到的数据。
7.2. Alert Protocol
alert表达了该消息的严重性(warning或fatal)并描述了该alert。fatal级别的alert会导致直接断开链路,在这种情况下,该session的其他链路可能会继续连接,但该session id会被标记为不可用,用来防止重建链接。跟其他消息一样,alert也会被压缩并加密。
代码语言:javascript复制struct {
AlertLevel level;
AlertDescription description;
} Alert;
7.2.1. Closure Alerts
client和server必须共享链路的终止状态,用来防止截断攻击。任何一方都可以发出初始化发送closure消息,接收到closure消息之后的所有数据都会被忽略。
close_notify:
该消息用于提示接收者,发送者后续不会在该链接上发送任何消息。除非发生fatal错误,链路的两端都应该在停止关闭链路前发送close_notify alert消息,并丢弃pending writes。
更多参见rfc2818
7.2.2. Error Alerts
TLS Handshake协议处理错误的方式很简单,当一方发现错误时,会将该消息发往对端。当发送或接收该消息时,两端必须立即断开链接,并删除所有与链接相关的session信息(keys secrets等)。任何被fatal alert关闭的链接都不能重用。
当遇到无法判定的alert级别的错误,发送端可能会选择将其作为fatal级别的错误处理。如果实现中发送alert的目的是为了关闭链接,则必须发送fatal级别的alert。
当接收到warning级别的alert,通常可以继续保持链接。作为发送端,由于通常无法判断接收端接收到warning级别的alert的动作,因此通常warning的alert作用不大。已经定义的alert参见Error Alert
7.3. Handshake Protocol Overview
TLS Handshake协商产生session state所需要的加密参数。当clent和server开始通信时,会协商产生版本号,加密算法(可能会进行相互认证),以及使用公钥生成共享密钥。TLS Handshake包含如下步骤:
通过交换hello消息确定加密算法,交换随机数并检查session 是否重用
交换加密参数,来允许确定client和server使用的premaster secret
交换证书和加密信息来对client和server进行认证
使用premaster secret生成master secret并交换随机数
给Record层提供安全参数
client和server校验对端通过handshake计算出的安全参数
需要注意的是上层协议不能假设TLS可以始终提供健壮的安全链接。实际上有很多中间人攻击可以导致两端切换到它们所支持的最低级别的安全方法。TLS协议用来最小化攻击,但实际中存在很多可能的攻击方式,攻击者可能block安全服务所运行的端口,或者使得两端协商到非认证的链接。使用TLS的基本原则是上层协议必须知道所需要的安全需求,并且不能在低于安全需求的链路上传输信息。因此在使用满足需求的cipher suite前提下,可以认为链路是安全的。
这些通过Handshake协议来达成,基本过程如下:client发送ClientHello,server接收到后必须返回ServerHello(否则返回fatal的alert错误并断开链接)。ClietHello和ServerHello用来组建链路安全能力。ClientHello和ServerHello用来建立如下属性:Protocol Version,Session ID,Cipher Suite,以及Compression Method。除此之外,还需要生成并交换2个随机数:ClientHello.Random和ServerHello.Random。
实际交换key需要用到4个消息:server Certificate, ServerKeyExchange, client Certificate和ClientKeyExchange。可以通过定义这些消息的格式以及消息的用途来实现交换key的方法,最终client和server协商出共享密钥。该密钥必须足够长,当前的密钥交换方式交换的密钥长度在46字节以上。
hello消息完成之后,sever会在Certificate消息中发送其证书(当需要被认证时),除此之外,可能会发送ServerKeyExchange消息。当server端被认证后,server可能会要求对client进行认证,接下来server会发送ServerHelloDone消息来表示hello阶段的握手结束,然后sever端会等待client端的响应。如果server发送了CertificateRequest消息,client必须发送其certificate消息。随后会发送ClientKeyExchange消息,该消息的内容取决于ClientHello和ServerHello协商选定的公钥算法。如果client发出了带签名能力的certificate,会发送数字签名的CertificateVerify消息来验证拥有certificate私钥的一方。
在client和server进行相互认证之后,client会发送ChangeCipherSpec消息,此时client会将pending state中的Cipher Spec拷贝到Current state中,然后立即使用最新的算法,keys和secrets发送加密的Finished消息,同时,server端会响应它的ChangeCIpherSpec消息,并将pending转变为current,使用新的CipherSpec发送Finished消息。到此为止,握手结束,后续client和server可以交换应用数据。应用数据不能先于首次握手(非TLS_NULL_WITH_NULL_NULL的cipher suite)发送。
代码语言:javascript复制 Client Server
ClientHello -------->
ServerHello
Certificate*
ServerKeyExchange*
CertificateRequest*
<-------- ServerHelloDone
Certificate*
ClientKeyExchange
CertificateVerify*
[ChangeCipherSpec]
Finished -------->
[ChangeCipherSpec]
<-------- Finished
Application Data <-------> Application Data
* 表示不一定发送的消息
ChangeCipherSpec是独立的TLS消息,不属于Handshake消息的一部分
当client和server决定重用先前的session会现有的session时,消息流程如下:
client发送ClientHello(带需要重用的session ID)。server会校验需要匹配该ID的session。如果匹配成功且server同意使用指定的session重建链接,会发送带该Session ID的ServerHello消息。此时client和server必须发送Change CIpher Spec消息并直接处理Finished消息。一旦重建成功,client和server就可以开始交换应用层数据;如果匹配失败,Sever会生成一个新的Session ID,并执行完整的握手过程。
代码语言:javascript复制 Client Server
ClientHello -------->
ServerHello
[ChangeCipherSpec]
<-------- Finished
[ChangeCipherSpec]
Finished -------->
Application Data <-------> Application Data
7.4. Handshake Protocol
TLS Handshake协议属于TLS Record协议的上层协议。该协议用于协商session的安全属性。Handshake消息在TLS record层之下运行,被封装成一个或多个TLSPlaintext结构,并被当前session的state处理。
handshake协议消息的发送顺序必须遵从如下规则:乱序的handshake消息会导致fatal错误;不需要的handshake消息可以被忽略;唯一一个不需要受发送顺序限制的消息是HelloRequest,但client端进行handshake过程中接收到后应该忽略该消息。
7.4.1. Hello Messages
hello阶段用来建立安全加密的能力。当新建一个session的时候,record层connection state的加密,哈希和压缩算法都初始化为null。
7.4.1.1. Hello Request
server端可以在任何时候发送HelloReques消息。
该消息用来通知client端进行协商,client会发送ClientHello消息进行响应。该消息不能用于判定哪一端开始建立链接(仅用于初始化协商)。如果client正在握手协商中或者client不同意建立链接,client会发送no_renegotiation alert。如果server发送HelloRequest并没有受到任何响应,Server可能会关闭链接并发送fatal alert。
该消息不能使用Handshake中(Finished消息和certificate消息)使用的hash。
7.4.1.2. Client Hello
client发送ClientHello消息来初始化握手协商,ClientHello也用于响应server发送的HelloRequest消息。
ClientHello包含一个随机数的结构体。如下
代码语言:javascript复制 struct {
uint32 gmt_unix_time;
opaque random_bytes[28];
} Random;
gmt_unix_time:是标准unix 32位格式的发送端的内部时钟时间。在TLS协议中没有规定该时钟的精确度。
random_bytes:生成的28字节的安全随机数。注:随机数并不是session ID
Client Hello消息包含一个可变长度的session id。如果该字段非空,该值表示了client需要重用的session,此时session id可以来自于先前的链接(1),当前的链接(2)或其他当前的链接(3)(其他session的)。第2种用于当client仅需要更新随机数的场景;第3中用于建立独立的安全链接(而无需进行完整的握手过程)。当握手协商通过交换Finished后保存的sessionID超时,或遇到fatal错误时,sessionID会变为无效状态。Session ID的实际内容由server端定义。
由于SessionID的传输没有经过加密或MAC保护(因为此时加密参数还未协商出来),sever端不能在session id中放置敏感字段,否则可能导致安全攻击。在握手结束后的消息(含Finished消息)中的session ID会被加密保护。
ClientHello中的cipher suite列表给出了client支持的加密算法,并按照client偏好排序。每个cipher suite包含一个交换算法key,块加密算法,MAC算法和PRF。sever会选择一个合适的cipher suite,如果没有合适的则会发送handshake failure alert并关闭链接。如果cipher suite中包含server无法识别的项,server端必须忽略这些cipher suites,并处理剩余的cipher suites。
代码语言:javascript复制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;
ClientHello通过判断在compression_method之后是否有多余的字节来确定是否由存在扩展字段。
server_version:client建议的最低版本且server支持的最高版本,本协议使用值为3.3。TLS1.2中如果sever不支持该版本,会返回带有低版本的ServerHello,如果client同意使用该低版本,则使用该版本进行协商,否则client返回protocol_version alert并关闭链接。如果server接收到的ClientHello携带了高于server支持的最高版本的版本,则必须返回server所支持的最高版;当server接收的ClientHello中的版本低于server所支持的最高版本,如果server同意使用该低版本,则会选择不高于ClientHello.client_version中定义的最高版本(如,server支持TLS1.0 1.1和1.2,client_version为TLS 1.0,则server会使用TLS1.0处理),反之返回protocol_version alert。
random:client生成的随机数。
session_id:非空表示重用session,空表示新建session
cipher_suites:如果session_id非空,该字段至少需要包含该session已经协商好的cipher_suite(session重用)
compression_methods:client支持的压缩方法,按照偏好排序。如果session id非空,该字段至少需要包含该session已经协商好的compression_method。所有的实现必须支持CompressionMethod.null。
extensions:client可能会通过扩展字段来请求server的扩展功能。
当client使用扩展请求额外的功能,但server不支持时,client可能会断开握手。server端必须接收带或不带扩展字段的ClientHello,并解析这些扩展字段,如果发现无法解析的字段,则必须返回decode_error alert。
client发送完ClientHello后会等待server端的ServerHello,非ServerHello(HelloRequest除外)的消息会被认为fatal错误。
7.4.1.3. Server Hello
在server接收到ClientHello且能够接受其中列出的cipher suite时会发送ServerHello
代码语言:javascript复制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;
server_version:与client的定义一致
random:server端生成的随机数,独立于client的随机数
session_id:server可能会发送空的session id,表示session不会被缓存且不会被重用。
其余字段与client一致
7.4.1.4. Hello Extensions
扩展字段的格式如下:
代码语言:javascript复制struct {
ExtensionType extension_type;
opaque extension_data<0..2^16-1>;
} Extension;
enum {
signature_algorithms(13), (65535)
} ExtensionType;
extension_type:表示特定的扩展类型
extension_data:表示特定的扩展的信息
IANA维护的扩展参见Section 12
只有在ClientHello中出现的扩展才能出现在ServerHello中。如果client接收到的ServerHello中出现了ClientHello中不相关的扩展,则client必须断开链接并发送unsupported_extension fatal alert。未来可能会实现“server-oriented”的扩展,参见Hello Extensions
当ClientHello或ServerHello消息中存在多个扩展时,扩展的可以以任意顺序存在,但不能出现同一扩展类型的多个实例。
扩展可以在初始化新的session或重用session时发送。在client请求重用session时,由于它并不知道server端是否会支持不同的扩展,这种情况下,client会发送之前发送过的扩展。
每个扩展类型需要规定在完整的握手和session重用场景下的作用。目前大部分TLS 扩展的实现中仅关注session初始化:当重用先前session的时候,sever不会处理ClientHello中的扩展,也不会将他们保存在ServerHello中。
开发extension的注意点参见Hello Extensions
7.4.1.4.1. Signature Algorithms
client使用signature_algorithm扩展来告诉server端用于数字签名时使用的signature/hash算法对。该扩展的"extension_data'字段包含"supported_signature_algorithms"值。
代码语言:javascript复制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>;
每个SignatureAndHashAlgorithm列表包含一个hash/signature对(不是所有的hash和signature都能配对),按照偏好排序。
hash:表明需要使用的哈希算法,如MD5,SHA-1,SHA-224,SHA-256,SHA-384,SHA-512等。“none”表示为以后扩展使用,也用于仅使用signature但不使用hash的场景。
signature:表明需要使用的签名算法,如RSASSA-PKCS1-v1_5,DSA,ECDSA等,该字段必须出现在该扩展中。
由于Cipher Suite中也定义了允许的签名算法(但没有定义哈希算法),因此在结合该扩展使用时会变得比较复杂,参见7.4.2和7.4.3章节
如果client仅支持默认的hash和signature算法,则可能忽略signature_algorithm扩展。如果client不支持默认算法,或支持其他hash/signature算法,则必须发送signature_algorithm扩展并列出接受的算法。
如果client没有发送signature_algorithm扩展,则server必须:
如果协商的密钥交换算法为RSA, DHE_RSA, DH_RSA, RSA_PSK, ECDH_RSA, ECDHE_RSA其中之一,则认为client发送了{sha1,rsa}
如果协商的密钥交换算法为DHE_DSS, DH_DSS其中之一,则认为client发送了{sha1,dsa}
如果协商的密钥交换算法为ECDH_ECDSA, ECDHE_ECDSA其中之一,则认为client发送了{sha1,ecdsa}
TLS1.2之前的版本无法识别该扩展。
server不能发送该扩展,但server必须支持接收该扩展
当重用session时,ServerHello中不能包含该扩展,且server忽略ClientHello中的该扩展。
7.4.2. Server Certificate
当两端需要使用基于证书的认证时,server端必须发送Certificate消息。该消息总是在ServerHello中发送。
Certificate消息会发送server的证书链。证书必须符合协商出来的密钥交换算法和扩展。
代码语言:javascript复制opaque ASN.1Cert<1..2^24-1>;
struct {
ASN.1Cert certificate_list<0..2^24-1>;
} Certificate;
certificate_list:证书链。发送者的证书必须排在第一个,后续的证书依此(直接)认证前面一个。因为证书认证要求root keys独立发布,自签证书中指定的root CA可能会被忽略,因此远端必须拥有自签证书的root key来进行验证。
客户端响应certificate request消息的消息类型和结构与server相同。当client没有合适的证书时,可能不会发送证书。server端使用证书时需要满足如下要求:
证书必须是X509v3,除非明确协商使用其他类型的证书
证书的公钥必须能够兼容协商的密钥交换算法
密钥交换算法 证书key类型
RSA/RSA_PSK RSA公钥,证书必须能够用于加密(当出现key esage扩展时,keyEncipherment必须置位)
DHE_RSA/ECDHE_RSA RSA公钥,证书必须能够用于签名(当出现key esage扩展时,digitalSignature必须置位)
DHE_DSS DSA公钥,证书必须能够用于签名(结合server key exchange消息中出现的hash算法)
DH_DSS/DH_RSA Diffie-Hellman公钥(当出现key esage扩展时,keyAggrement必须置位)
ECDH_ECDSA ECDH-capable公钥,该公钥必须使用client支持的curve和point格式
ECDHE_ECDSA ECDSA-capable公钥,证书必须允许key进行签名且必须使用client支持的curve和point格式
“server_name”和“trusted_ca_keys”扩展用于指导证书选择。
如果client提供了signature_algorithm扩展,那么server提供的所有证书都必须使用该扩展中出现的hash/algorithm算法对进行签名(数字签名),这也意味着包含某种签名算法密钥的证书可能被不同的签名算法进行签名(如RSA key使用DSA key进行签名)。这也是与TLS1.1不同的地方。
如果server拥有多个证书,client会根据上述原则(除此之外,还有如本地配置和偏好)挑选其中一个进行验证;如果server只有一个证书,则必须满足所有规则。
7.4.3. Server Key Exchange Message
该消息会在server certificate之后发送,且仅在server Certificate消息不能够提供用于client交换premaster secret的数据时发送。下面密钥交换算法会发送该消息
DHE_DSS DHE_RSA DH_ann
下面交换算法不能发送该消息
RSA DH_DSS DH_RSA
该消息用来给client提供后续传输premaster secret的加密信息。client端使用Diffie-Hellman公钥配合该消息承载的数据可以完成premaster secret交换(或其他算法功能)。
代码语言:javascript复制 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 */
dh_p
The prime modulus used for the Diffie-Hellman operation.
dh_g
The generator used for the Diffie-Hellman operation.
dh_Ys
The server's Diffie-Hellman public value (g^X mod p).
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;
params:serverkey exchange的参数,如下
signed_params:用于非匿名密钥交换,对server交换的密钥参数的签名。可以看到,签名的内容包含client和server的随机数以及交换算法的参数
如果client提供了signature_algorithm扩展,则该消息中必须使用client的signature_algorithm扩展中出现的hash/algorithm对(下面红框中的hash/algorithm算法是client 支持的)。但在实际使用中会出现不一致的情况,如client提供了DHE_DSS密钥交换但在signature_algorithm扩展中却忽略所有的DSA(即扩展中忽略了cipher suite中定义的签名算法)。为了能够正确地协商,server必须在选择前校验所有的cipher suites与signature_algorithms(即cipher suites中的签名算法与扩展不一致时)。除此之外,hash/signature算法必须与server端证书中的key相匹配。RSA keys可能与任何hash算法兼容(签名与哈希组合的方式受限于证书)。
7.4.4. Certificate Request
当非匿名server需要对client进行认证时会发送该消息,跟在ServerKeyExchange后面。
代码语言:javascript复制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>;
SignatureAndHashAlgorithm
supported_signature_algorithms<2^16-1>;
DistinguishedName certificate_authorities<0..2^16-1>;
} CertificateRequest;
certificate_type:client可能会提供的证书类型
rsa_sign:包含RSA key的证书
dss_sign:包含DSA key的证书
rsa_fixed_dh:包含static DH key的证书
dss_fixed_dh:包含static DH key的证书
supported_signature_algorithms:server支持的hash/signature算法,按偏好排序
certificate_authorities:可接受的CA的distinguished names(DN),DER编码格式。这些DN字段可能会指定一个root CA或子CA期望的DN,因此该消息可以用来描述已知的根以及期望的授权范围。如果certificate_authorities列表为空,则client可能会选择发送合适的ClientCertificateType类型的证书。
certificate_types和supported_signature_algorithms字段交互比较复杂,certificate_types在SSLv3版本中出现,但仍然待确定,它的大部分功能被supported_signature_algorithms取代。规则如下:
client提供的证书必须使用supported_signature_algorithms中定义的hash/signature算法进行签名
client提供的证书必须包含于certificate_types兼容的key,如果key是一个签名key,则该key必须能配合supported_signature_algorithms中的hash/signature算法使用。
由于历史原因,一些client证书类型包含用于签名这些证书的算法。如在早期的TLS版本中,rsa_fixed_dh表示使用RSA签名的证书且该证书包含一个static DH key。在TLS 1.2中,该功能被(supported_signature_algorithms)废弃,证书类型不再限制用于签名证书的算法。如,server发送的dss_fixed_dh类型的证书,以及{{sha1, dsa}, {sha1, rsa}}的签名类型,client可能会回复带DH key的证书,使用RSA-SHA1签名。
7.4.5. Server Hello Done
server发送该消息用来表示完成了key exchange的消息的交互,client可以进入密钥交互阶段。client在接收到ServerHelloDone消息后,会对server提供的证书进行校验,如果证书校验通过则继续对server的hello参数进行校验。
7.4.6. Client Certificate
该消息是client接收到ServerHelloDone消息之后发送的第一个消息,且只在server请求证书时发送。如果没有合适的证书,client也必须发送该消息,消息中不包含任何证书,即certificate_list长度为0。如果client没有发送任何证书,server可能会握手(不对client进行认证),也可能会返回handshake_faiure fatal alert。如果证书链的某些地方不可接受(如签名的CA不可知),server可能会继续握手或返回fatal alert。
该消息会传递client的证书链,server会使用该消息来校验CertificateVertify消息或计算premaster secret(non-ephemeral Diffie- Hellman)。证书必须匹配协商的cipher suite的密钥交换算法以及扩展。
证书类型必须是X509v3(除非明确指出使用其他类型的证书)。
client证书必须于CertificateRequest中的证书类型匹配。
client证书类型 证书key类型
rsa_sign RSA公钥,证书必须能够用于签名(结合certificates verify消息中的hashsignature算法)
dss_sign DSA公钥,证书必须能够用于签名(结合certificates verify消息中的hashsignature算法)
ecdsa_sign ECDSA-capable公钥,证书必须能够用于签名(结合certificates verify消息中的hashsignature算法);公钥必须使用server支持的curve和point格式
rsa_fixed_dh/dss_fixed_dh Diffie_Hellman公钥,必须与server key的premaster相同
rsa_fixed_ecdh/ecdsa_fixed_ecdh ECDH-capable公钥,必须与server key的curve相同,必须使用server支持的point格式
如果certificates 请求中的certificate_authorities (DN)列表非空,则CA列表中的某个CA应该签发了证书链中的某个证书。
证书必须使用可接受的hash/algorithm进行签名(与7.4.4章节相同)。
7.4.7. Client Key Exchange Message
该消息在certificate之后发送,如果没有发送certificate消息,则在ServerHelloDone之后发送。
发送该消息表示premaster secret已经生成(通过直接传输RSA加密的premaster secret或传递用于两端生成相同premaster secret的Diffie_Hellman参数)
当client使用ephemeral Diffie-Hellman exponent进行密钥交换时,该消息包含了client的Diffie-Hellman public值;如果client发送的certificate包含static DH exponent,则该消息必须发送,但必须为空
代码语言:javascript复制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;
7.4.7.1. RSA-Encrypted Premaster Secret Message
RSA用于key协商和认证,client会生成48字节的premaster secret,并将premaster secret使用server 证书提供的公钥进行加密,最后发送到server端。该结构是ClientKeyExchange的变种,且不是一个独立的消息。
代码语言:javascript复制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:client用于生成master secret的随机值。PreMasterSecret中的版本号由ClientHello.client_version提供,而非协商的链路版本,该设计用于防止rollback攻击。不幸的是有些老的实现会使用协商出的版本号,与这些实现交互时可能会失败。
client的实现中必须在PremasterSecret中发送正确的版本号。如果ClientHello.client_version为TLS1.1或更高,server的实现中必须检查该版本号;如果版本号为1.0或更早,server可能会检查版本号,也可能不检查。
TLS server在加密premaster secret失败或遇到非期望的版本号时不能发送alert,而应该使用一个随机生成的premaster secret进行握手。
公钥加密的数据是一个0~2^16-1长度的opaque向量,因此ClientKeyExchange中使用RSA加密的PreMasterSecret之前为2个字节的长度字段(表示其可变长度大小)。EncryptedPreMasterSecret 是ClientKeyExchange的唯一数据。
7.4.7.2. Client Diffie-Hellman Public Value
当client的certificate中没有出现Diffie-Hellman public的值时发送,该消息是ClientKeyExchange的变体。当certificate中已经包含合适的Diffie-Hellman key(用于fixed_dh client认证),此时必须于发送client key exchange消息,但必须为空。
7.4.8. Certificate Verify
该消息用于提供client证书的校验,证明client拥有这些证书的私钥。仅在client certificate由签名功能时发送(除了包含fixed Diffie-Hellman参数的证书),跟在client key exchange 消息之后发送。
代码语言:javascript复制struct {
digitally-signed struct {
opaque handshake_messages[handshake_messages_length];
}
} CertificateVerify;
这里的handshake_messages表示从ClientHello开始的所有接收和发送的握手消息(不包含本消息),该消息级联了所有握手消息,结构如下
代码语言:javascript复制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;
该实现会要求两端缓存该消息或计算出(到接收到CertificateVerity为止的)所有可能的hash算法。server可以通过在CertificateRequest中设置限制来减小运算。
签名的hash/signature算法必须是CertificateRequest消息中提供的。除此之外,hash/signature算法必须与client端的certificate兼容。
7.4.9. Finished
该消息在ChangeCipherSpec消息之后发送,ChangeCipherSpec表示密钥交换和认证都已经完成。
Finish是第一个使用协商的算法保护的消息,key和secrets保护的消息,接收到该消息之后必须验证消息内容的有效性,在认证成功后就可以发送应用消息。
代码语言:javascript复制struct {
opaque verify_data[verify_data_length];
} Finished;
verify_data:PRF(master_secret, finished_label, Hash(handshake_messages)) 0..verify_data_length-1;在旧版本中的TLS,verify_data为12 octets,当前版本则却决于使用的cipher suite。任何没有明确指定verify_data_length的verify_data_length默认为12 octets。
finished_label:client发送的Finished消息相关的字符串为"client finished',server发送的为“server finished”
Hash:表示握手消息的hash,该hash与PRF使用的hash相同。cipher suite定义的PRF必须定义用于FInished计算的Hash
handshake messages:握手过程中的所有消息(除HelloRequest外)。只包括handshake层的可见数据,不包含Record层首部。
在握手过程中,如果没有在合适的时机处理Finished消息,则返回fatal 错误。值handshake_message与7.4.8章节中定义的handshake_message不同,它包含了CertificateVerify消息, 且client的handshake_message与server的也不相同(因为发送消息的一端会包含对端已经发过来的消息)
ChangeCipherSpec,alert和其他Record类型的消息不是握手消息,且不会进行hash运算(HelloRequest也不会)
A.5. The Cipher Suite
本节定义了ClientHello和ServerHello的cipher suite使用的cipher suite。TLS初始化的cipher suite为TLS_NULL_WITH_NULL_NULL,但不能使用该值进行协商(没有提供任何保护)
代码语言:javascript复制CipherSuite TLS_NULL_WITH_NULL_NULL = { 0x00,0x00 };
如下CipherSuite需要server提供用于密钥交换的RSA证书。server可能会在CertificateReuqest中请求signature-capable证书。
代码语言:javascript复制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 };
如下Cipher suite用于。DH表示server的证书包含CA签名的Diffie-Hellman参数。DHE表示 ephemeral Diffie-Hellman,此时使用signature-capable证书(该证书u也被CA签名)签名Diffie-Hellman参数。server使用的签名算法在Cipher Suite的DHE名字之后。
代码语言:javascript复制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 };
server可以向client请求signature-capable证书或DH证书来对client进行认证。client提供的所有Diffie-Hellman证书必须使用server提供(或生成)的参数
如下cipher suite用于在两端都没有认证情况下的匿名Diffie-Hellman交互。该模式容易遭中间人攻击,因此使用场景比较有限。该模式不能用于TLS1.2的实现中(除非明确指出允许匿名key交互)。
代码语言:javascript复制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 };
目前使用最多的cipher suite:TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256解析如下:
ECDHE为密钥交互算法,RSA为签名算法,AES_123_GCM为使用GCM模式下的128位AEAD加密算法,SHA256为创建消息摘要的MAC算法
CipherSuite的详细定义可以参考Cipher suite definitions
带RC4的表示使用流加密
Appendix B. Glossary
Advanced Encryption Standard (AES):对称加密算法,块加密。TLS当前仅支持128-和256-位长度的AES
authenticated encryption with additional data (AEAD):对称加密算法
block_cipher:块加密作用于一组比特位的纯文本算法,之前使用64bit,目前使用128bit,通用的块大小
cipher block chaining (CBC):在每个纯文本块加密前会于前面一个纯文本(第一个块与初始化向量-IV 进行运算)进行异或运算。解码时首先进行解密,再与前面文本块进行异或运算
Digital Signature Standard (DSS):数字签名算法
digital signature:数字签名使用公钥和单向hash函数来产生签名数据。
Data Encryption Standard:普遍使用的对称加密算法
Initialization Vector (IV):当块加密使用CBC时,首个文本块会与IV进行异或运算。
client write key:用于加密client的数据
client write MAC key:用于认证client的数据
Message Authentication Code (MAC):单向hash处理的数据,生成消息摘要。
master secret:用于生成加密key,MAC secrets和IVs
RC4:流加密。
session:session定义了可与多个链接共享的安全参数。用来避免为每个链接进行安全参数协商造成的消耗。
stream cipher:将一个key加密为keystream,并与文本进行异或运算。
F.1.1.2. RSA Key Exchange and Authentication
使用RSA,key exchange和server认证可以同时进行。server的证书中包含了公钥。注意,使用静态的RSA key可能导致该静态key保护的所有session失效。
client验证完server的证书后,会使用证书中的公钥对pre_master_secret加密。server端成功解密pre_master_secret之后会并处理Finished消息之后,server认为client对server的认证成功。
当使用RSA进行密钥协商时,client通过certificate verify消息进行认证。client会对所有处理的握手消息进行签名,这些握手消息包括server certificate(该消息使用的签名与server相关)以及ServerHello.random(该数值与当前握手交互使用的签名相关)
F.1.1.3. Diffie-Hellman Key Exchange with Authentication
当使用Diffie-Hellman密钥交换时,server可以提供包含fixed Diffie-Hellman参数的证书或使用server KeyExchange消息发送一系列使用DSA或RSA签名的临时DH参数。这些临时参数在签名前会使用hello.random进行hash,用以防止攻击者使用老参数进行攻击。其他场景下,client可以通过验证证书和签名来确保该消息来自server。
如果client有包含fixed Diffie-Hellman参数的证书,该证书中的信息可以用于完成密钥交互。这种情况下,client和server会生成相同的DH结果(pre_master_secret)。为了防止pre_master_secret过长地滞留在内存中,在完成它的计算后应该尽快将其转化为master secret。client 的DH参数必须兼容server端的DH参数(key exchange交换而来)。
如果client使用DSA或RSA证书或其未被认证,它会在clientKeyExchange消息中发送临时的参数,后续可能使用certificate verify消息对其进行认证。
如果使用DH密钥对进行多个握手,由于client和server都拥有一个包含fixed DH密钥对的证书或server重用DH密钥,需要注意方式subgroup攻击。
小型的subgroup攻击可以通过使用DHE cipher suite并未每个握手生成新的DH私钥来解决。
由于TLS允许serve提供任意的DH groups,client应该对DH group进行校验(与本地策略比较)。
F.1.3. Detecting Attacks Against the Handshake Protocol
攻击者可能会通过握手消息来修改两端使用的加密算法。因此,攻击者必须修改握手过程中的一个或多个消息,当发生这种情况的时候,client和server会计算出不同的hash值(消息摘要改变),此时两端不会接受对端的Finished消息。由于无法获取master_secret,攻击者无法修复Finished消息,这样就可以发现攻击。
F.1.4. Resuming Sessions
当需要重用session来建立链接时,会使session的master secret生成新的ClientHello.random和SeverHello.random。
只有在client和server双方同意的情况下才能重用session。如果一端拒绝或证书过期(或被撤销),此时应该进行完整的握手。
F.2. Protecting Application Data
master secret(使用ClientHello.random和ServerHello.random生成)用于生成加密数据的keys和MAC secrets
代码语言:javascript复制master_secret = PRF(pre_master_secret, "master secret",ClientHello.random ServerHello.random)
使用MAC保护发送的数据。为了方式消息重复和篡改,使用MAC key,序列号,消息长度,消息内容以及2个固定字符串生成MAC 。消息类型字段用来确保该消息服务于某个TLS Record层。序列号用来确保能够感知到消息的删除和重复。由于序列号为64位长度,可以保证数值不会被溢出。由于使用了独立的MAC keys,一方的消息不能插入到另一方的输出中(插入后MAC会错误)。类似地,由于server write和client wirte keys是独立的,因此只会用到一次流加密keys。
代码语言:javascript复制MAC(MAC_write_key, seq_num TLSCompressed.type TLSCompressed.version TLSCompressed.length TLSCompressed.fragment);
如果攻击者获取了加密key,那么所有的加密消息都会被读取。类似地,泄露MAC key会导致message-modification攻击。由于MAC是加密的,message-alteration同时也需要突破对MAC的加密算法。
F.4. Security of Composite Cipher Modes
TLS使用cipher suite中定义的对称加密和认证函数对传输的应用数据进行保护,目的是防止网络攻击造成数据的篡改。
当前最健壮的方式称为encrypt-then-authenticate(加密再认证),首先对数据进行加密,然后对密文使用MAC计算消息摘要。该方法使用加密和MAC函数保证数据的加密防护和完整性。前者用于防止针对明文的攻击(信息泄露),后者用于防止针对消息的攻击(链路攻击)。其次,TLS使用其他方式,称为authenticate-then-encrypt(认证再加密),首先先对明文使用MAC,然后对整个数据(明文 MAC)进行加密。这种方式已经通过使用特定加密函数和MAC函数的组合证明了其安全能力,但通常不能保证安全。特别地,在使用当前非常好的加密函数再结合某个MAC函数的情况下,无法很好地防御主动攻击。
目前已经证明在某些情况下更加适合使用authenticate-then-encrypt。其中一种情况是使用流加密时,消息pad MAC tag的长度不可知的情况;另一种时在CBC模式下使用块加密。这些情况下,安全能力可以通过一方对明文 MAC使用CBC加密以及对每个明文和MAC使用(新的,独立的,不可预测的)IV体现出来。
TLS使用块加密 HMAC,流加密 HMAC以及AEAD(Authenticated-Encryption With Additional data)来保证数据的完整和安全。TLS1.3中废除了除AEAD外的加密方式
TIPS:
- 实际中基本不使用流加密方式
- key exchange的目的是生成pre_master_secret,最终生成master secret,通过Finished来校验pre_master_secret的正确性。
- 数字签名过程为发送端首先使用hash算法生成消息摘要,然后使用签名算法生成数字签名;接收端为上述逆过程
参考:
https://blog.helong.info/blog/2015/09/07/tls-protocol-analysis-and-crypto-protocol-design/
http://blog.fourthbit.com/2014/12/23/traffic-analysis-of-an-ssl-slash-tls-session
https://www.linuxidc.com/Linux/2015-07/120230.htm
https://blog.cloudflare.com/keyless-ssl-the-nitty-gritty-technical-details/
https://segmentfault.com/a/1190000002554673?utm_source=tag-newest
https://blog.helong.info/blog/2015/01/23/ssl_tls_ciphersuite_intro/
https://httpd.apache.org/docs/2.4/ssl/ssl_intro.html