TSL由多个协议组成的两层协议集合,工作与应用层和传输层之间。
TLS协议包含两层协议:记录层协议(TLS Record Protocol协议)和 握手协议(TLS Handshake Protocol协议),底层采用可靠传输协议(如TCP)
之所以选取这两个子协议,是因为记录层协议发挥了TLS“承上启下”的作用,它对从上层应用接收到的待传输数据进行分块、压缩、封装等处理,而后将处理后的数据传输给下层,再传输给通信的另一方;而握手协议是通信双方准备建立TLS连接通信时,用到的第一个子协议,他是TLS协议中最复杂、涉及密码技术最多的协议。
注:出于安全考虑,TLS 1.3已取消压缩
记录层协议通过如下方式实现数据的安全传输:
- 链路是私有的,使用对称加密方式对应用数据进行加密。对每条链路来说,对称加密使用的密钥是各自独立的(通过握手协议协商得到)。记录层协议也可以用于非加密场景;
- 链路是可靠的。使用消息认证码(MAC)来对消息完整性进行校验,MAC使用哈希函数(SHA-1、SHA-256、SM3等)进行运算。记录层协议可以不使用MAC,但通常仅限于使用记录层协议作为底层协议来传输协商使用的安全参数。
记录层协议用于封装多种上层协议,如握手协议,用于在传输/接收应用数据前进行相互认证,并协商出加密算法以及使用的密钥。
握手协议使用如下三种方式提供数据的安全传输:
- 对端身份可以通过非对称算法或公钥,加密(RSA、DSA、SM2等)等进行认证。该认证是可选的,但通常要求至少通过一种认证方式对对端进行认证;
- 协商的共享密钥的过程是安全的,窃听者无法获取协商的密钥;
- 协商是可靠的,攻击者无法在不被链路探测到的情况下修改协商报文。
使用TLS的好处是它与应用层协议是相互独立的,上层协议运行在TLS协议之上。TLS协议没有指出如何添加协议来对链路进行安全加固。如何初始化TLS握手以及如何使用认证的证书,这些交由TLS之上的协议设计者来实现。
1.HMAC and the Pseudorandom Function(带密钥哈希消息认证码&伪随机函数)
TLS使用MAC来保证消息的合法性。本协议使用的密码套件(cipher suites)使用了HMAC,它的实现基于hsah函数,其他密码套件可能使用其他形式的MAC。
注:在TLS 1.3之前,密码套件的名称是以协商安全设置时使用的身份验证、加密、消息认证码和密钥交换算法组成。TLS 1.3中的密码套件格式已经修改。在TLS 1.3草案文档中,密码套件仅用于协商加密和HMAC算法。
除此之外,还需要一种在生成或校验密钥时将密钥扩展为数据块的方式。伪随机函数(pseudorandom function;PRF)可以通过输入密钥、种子、认证的标签(即预主密钥、客户端随机数、服务端随机数、常量字符串)之后给出任意长度的输出。伪随机函数基于HMAC。本文档所有密码套件使用的的伪随机函数均采用SHA-256的哈希函数。新的密码套件必须明确规定伪随机函数,并使用SHA-256或更健壮的hash函数。
密码套件规定了如何使用伪随机函数生成密钥材料(key material),块加密算法(AES等)以及MAC算法(HMAC-SHA1)。
每个密码套件的名称(例如TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256)定义密钥交换算法、批量加密算法、消息认证码算法,以及一个伪随机函数。
- 密钥交换算法,例如ECDHE_RSA,用于决定客户端与服务器之间在握手时如何身份验证。
- 批量加密算法,例如AES_128_GCM,用于加密消息流。它还包括密钥大小及显式和隐式初始化向量(密码学随机数)的长度。
- 消息认证码算法,例如SHA256,用于创建消息摘要,消息流每个数据块的加密散列。
- 伪随机函数,例如TLS 1.2的伪随机函数使用MAC算法的散列函数来创建一个主密钥——连接双方共享的一个48字节(368比特)的私钥。主密钥在创建会话密钥(例如创建MAC)时作为一个熵来源。
使用伪随机函数首先需要定义一个扩展密钥的函数,P_hash(secret,data),该函数用于扩展密钥长度
P_hash(secret, seed) = HMAC_hash(secret, A(1) + seed) +HMAC_hash(secret, A(2) + seed) +HMAC_hash(secret, A(3) + seed) + ...
其中:
- secret是进行计算所需要的密钥;
- seed是进行计算所需要的数据;
- A(0) = seed;
- A(i) = HMAC(secret,A(i-1));
- P_hash 能够反复迭代知道产生要求长度的数据
P_hash可以迭代多次来生成所需要的数据。如使用p_SHA256来生成80字节的数据,则需要迭代3次(32bytes * 3 = 96bytes)来生成96字节的数据,保留前面80字节,后16字节的数据会被丢弃。
使用P_hash生成伪随机函数的方式如下:
PRF(secret, label, seed) = P_<hash>(secret, label + seed)
label是ASCII字符串,如字符串 “slithy toves” 使用时应该为ASCII:73 6C 69 74 68 79 20 74 6F 76 65 73
2.The TLS Record Protocol(记录层协议)
记录层协议是一个层级协议,每一层消息可能包括长度(length)、描述(description)和内容(content)字段。记录层协议负责将需要传输的数据分布到管理的块(block)中,可能会对数据进行压缩(可选),在使用MAC和加密后传输结果。接收端接收到消息之后需要进行解密,校验和解压(可选)操作,然后传递到上层业务。当接收到未知的Record类型后,必须发送意外消息警报(unexpected_message alert)。
本文档定义了4种使用记录层协议协议的协议,握手(handshake)协议、报警(alert)协议、密码规格变更(change cipher spec)协议、应用数据(application data)协议。
2.1 Connection States(连接状态)
记录层协议使用连接状态(TLS connection state)作为运行环境,连接状态指定了压缩算法、加密算法以及MAC算法,且拥有消息认证码密钥(MAC key)和批量加密密钥(bulk encryption keys)相关的参数。记录层协议始终保持4个连接状态:当前读(current read)、当前写(current write)、挂起读(pending read)、挂起写(pending write)。其中,读表示接收数据,写表示发送数据。所有的记录都在当前读和当前写状态下处理。挂起状态(pending state)可以被握手协议修改,而密码规格变更消息可以选择性地将挂起状态变为当前状态(current state),此时挂起状态初始化为空状态(empty state)。给未初始化的状态配置参数并使之变为当前状态的方式是非法的。除了最初的当前状态外,当前状态必须包含经过协商的安全参数。初始的当前状态没有使用加密、压缩、MAC。
连接状态用于跟踪加密状态。写密钥(write key)用来发送数据,读密钥(read key)用来接收数据。挂起状态用来保存新的加密密钥(可以由握手协议生成)以及初始向量,而当前状态则表示当前使用的密钥,当接受到密码规格变更消息时,会将挂起状态的密钥覆盖当前状态的密钥,这样就会使用更新的密钥进行数据的收发。当更多关于连接状态的介绍可以参考:What are read state and write state in SSL connections
连接状态的结构如下:
TLS1.1 struct {
连接端(ConnectionEnd) 实体(entity);----标识接入的实体为客户端或服务端;enum{server, client} ConnectionEnd
批量密码算法(BulkCipherAlgorithm) bulk_cipher_algorithm;----加解密密码算法;enum{null, 对称算法} ConnectionEnd
密码类型(CipherType) cipher_type;----表示密码算法的类型;enum {block} CipherType
uint8 key_size;----密钥长度
uint8 key_material_length;----密钥材料长度
MAC算法(MACAlgorithm) mac_algorithm;----用于计算和校验的密码杂凑算法;enum{杂凑算法}MACAlgorithm
uint8 hash_size;----杂凑长度
压缩方法(CompressionMethod) compression_algorithm;----数据压缩算法;enum{null(0), (255)} CompressionMethod
opaque master_secret[48];----在协商过程中由预主密钥、客户端随机数、服务端随机数计算出的 48 字节密钥
opaque client_random[32];----由客户端产生的32字节
opaque server_random[32];----由服务端产生的32字节
} 安全参数(SecurityParameters);
TLS1.1记录层使用安全参数来生成如下6项内容:
- 客户端写校验密钥(client write MAC secret)
- 服务端写校验密钥(server write MAC secret)
- 客户端写密钥(client write key)
- 服务端写密钥(server write key)
GMSSL struct {
连接端(ConnectionEnd) 实体(entity);----标识接入的实体为客户端或服务端;enum{server, client} ConnectionEnd
批量密码算法(BulkCipherAlgorithm) bulk_cipher_algorithm;----加解密密码算法;enum{null, sm1, sm4} ConnectionEnd
密码类型(CipherType) cipher_type;----表示密码算法的类型;enum {block} CipherType
uint8 key_size;----密钥长度
uint8 key_material_length;----密钥材料长度
MAC算法(MACAlgorithm) mac_algorithm;----用于计算和校验的密码杂凑算法;enum{sha1, sm3}MACAlgorithm
uint8 hash_size;----杂凑长度
压缩方法(CompressionMethod) compression_algorithm;----数据压缩算法;enum{null(0), (255)} CompressionMethod
opaque master_secret[48];----在协商过程中由预主密钥、客户端随机数、服务端随机数计算出的 48 字节密钥
opaque client_random[32];----由客户端产生的32字节
opaque server_random[32];----由服务端产生的32字节
unit8 record_iv_length;----记录IV长度
unit8 mac_length;----MAC长度
} 安全参数(SecurityParameters);
GMTLS记录层使用安全参数来生成如下4项内容:
- 客户端写校验密钥(client write MAC secret)
- 服务端写校验密钥(server write MAC secret)
- 客户端写密钥(client write key)
- 服务端写密钥(server write key)
TLS 1.2 struct {
连接端(ConnectionEnd) entity;----标识接入的实体为客户端或服务端;enum{server, client} ConnectionEnd
伪随机算法(PRFAlgorithm) prf_algorithm;----通过主密钥(master secret)生成密钥的算法;enum {tls_prf_sha256}PRFAlgorithm
批量密码算法(BulkCipherAlgorithm) bulk_cipher_algorithm;----加解密密码算法;enum{null, 对称算法} ConnectionEnd
密码类型(CipherType) cipher_type;----表示密码算法的类型;enum {block} CipherType
uint8 enc_key_length;----加密密钥长度
uint8 block_length;----块长度
uint8 fixed_iv_length;----固定IV长度
uint8 record_iv_length;----记录IV长度
MAC算法(MACAlgorithm) mac_algorithm;----用于计算和校验的密码杂凑算法;enum{杂凑算法}MACAlgorithm
uint8 mac_length;----MAC长度
uint8 mac_key_length;----MAC密钥长度
压缩方法(CompressionMethod) compression_algorithm;----数据压缩算法;enum{null(0), (255)} CompressionMethod
opaque master_secret[48];----在协商过程中由预主密钥、客户端随机数、服务端随机数计算出的 48 字节密钥
opaque client_random[32];----由客户端产生的32字节
opaque server_random[32];----由服务端产生的32字节
} 安全参数(SecurityParameters);
TLS 1.2记录层使用安全参数来生成如下6项内容:
- 客户端写校验密钥(client write MAC key)
- 服务端写校验密钥(server write MAC key)
- 客户端写加密密钥(client write encryption key)
- 服务端写加密密钥(server write encryption key)
- 客户端写初始向量(client write IV)
- 服务端写初始向量(server write IV)
key_block = PRF(SecurityParameters.master_secret,"key expansion",SecurityParameters.server_random + SecurityParameters.client_random);
客户端写入(client write)参数由服务器在接收和处理记录时使用,反之亦然。2.3介绍了根据安全参数生成这些项目的算法。
一旦设置了安全参数并生成了密钥,就可以通过将连接状态设置为当前状态来实例化连接状态。必须为处理的每个记录更新这些当前状态。每个连接状态都包括以下元素:
- 压缩状态(compression state):压缩算法的当前状态。
- 密码状态(cipher state):加密算法的当前状态。这将包括该连接的计划密钥。对于流密码,它还将包含允许流继续加密或解密数据所需的任何状态信息。
- 校验密钥(MAC key):连接的校验密钥
- 序列号(sequence number):每个连接状态都包含一个序列号,且分别由读和写状态维护。当一个连接状态(connection state)变为活动状态(active state)时必须置为0。序列号会在每个记录中递增。
2.2Record Layer(记录层)
2.2.1Fragmentation(分段)
记录层将数据分成字节或者更小的片段。
每个片段结果如下:
struct {
内容类型(ContentType) type;----上层协议的类型,用于处理封装片段(fragment),结构详见①
协议版本(ProtocolVersion) version;----协议采用的版本,结构详见②
uint16 length;----TLS明文片段(TLSPlaintext fragment)的长度,不能大于
opaque fragment[TLSPlaintext.length];----将传输的应用数据,由上层协议处理的特定类型的数据,记录层协议不关心具体数据内容
} TLS明文(TLSPlaintext);
enum {
密钥规格变更(change_cipher_spec,20),
报警(alert,21),
握手(handshake,22),
应用数据(application_data,23),(255)
} ①内容类型(ContentType);
struct {
uint8 major;----主版本
uint8 minor;----次版本
} ②协议版本(ProtocolVersion);
不能发送零长度的握手、报警、密钥交换数据。但可以发送零长度的应用数据(可能用于链路探测)。
在TLS记录层协议中,应用数据处理的优先级很低,一般在上层协议(握手协议)处理完成后发送。
2.2.2Record Compression and Decompression(记录压缩和解缩)
所有的记录(record)都使用当前状态(current state)中定义的压缩算法进行压缩。初始的压缩算法为CompressionMethod.null(任何时候都有一个激活的压缩算法)。压缩算法将TLS明文(TLSPlaintext)结构转换为TLS压缩(TLSCompressed)结构。当连接状态(connection state)变为激活态(active)时,压缩算法会使用默认状态信息进行初始化。压缩算法不能造成数据丢失,且内容长度不能超过1024字节,当解缩函数解缩压缩片段(TLSCompressed.fragment)时,如果数据大于字节,必须返回解压失败(decompression failure)的致命错误。
压缩后数据结构如下:
struct {
内容类型(ContentType) type;----结构详见2.2.1①
协议版本(ProtocolVersion) version;----结构详见2.2.1②
uint16 length;----以字节为单位的TLS压缩片段(TLSCompressed.fragment)的长度,不大于字节
opaque fragment[TLSPlaintext.length];----TLS明文片段(TLSPlaintext.fragment)的压缩形式
} TLS压缩(TLSCompressed);
解压缩函数必须保证不能造成缓存(buffer)溢出;实际使用中一般禁用压缩
2.2.3Record Payload Protection(记录有效载荷保护)
加密和MAC函数会将TLSCompressed结构转化为TLSCiphertext结构,解密函数则反转上述过程。record的MAC包含序列号,因此能够探测到重复,多余,缺失消息的场景。
struct {
内容类型(ContentType) type;----结构详见2.2.1①
协议版本(ProtocolVersion) version;----结构详见2.2.1②
uint16 length;----以字节为单位的TLS密文片段(TLSCiphertext.fragment)的长度,不大于字节
select(安全参数密码类型(SecurityParameters.cipher_type) ){
case stream:GenericStreamCipher;---通用流密码
case block:GenericBlockCipher;---通用分组密码
case aead:GenericAEADCipher;---通用AEAD(Authenticated Encryption with Associated Data,关联数据的身份验证加密)密码
}fragment;----带校验码(MAC)的TLSCompressed.fragment加密形式
} TLS密文(TLSCiphertext);
2.2.3.1 Null or Standard Stream Cipher(空和流密码)
流密码将TLS压缩片段(TLSCompressed.fragment)结构转换为TLS密文片段(TLSCiphertext.fragment)的流(stream)结构,如下:
stream-ciphered struct{
opaque content [TLSCompressed.length];---TLS压缩(TLSCompressed)长度
opaque MAC [SecurityParameters.mac_length];---安全参数校验码算法(SecurityParameters.mac_algorithm)指定的MAC算法
}通用流加密(GenericStreamCipher)
MAC的生成方式如下,其中“+”表示串联:
MAC(MAC_write_key, seq_num(序列号) + TLSCompressed.type + TLSCompressed.version + TLSCompressed.length + TLSCompressed.fragment);
seq_num:序列号。每一个读写状态都分别维持一个单调递增序列号。序列号的类型为uint64,序列号初始值为零,最大不能超出。序列号不能回绕。如果序列号溢出,那就必须重新开始握手。
MAC必须在被加密前计算出来,流密码对包括MAC在内的整个块进行加密。对于不使用同步向量(synchronization vector)的流密码(如RC4),从一个记录末尾开始的流密码状态仅用于后续数据包。如果密码套件为TLS_NULL_WITH_NULL_NULL,加密方式则由操作类型组成(如数据没有被加密,MAC为0,即NULL的定义)。对于空(null)和流密码(stream ciphers)来说:TLS密文长度(TLSCiphertext.length) = TLS压缩长度(TLSCompressed.length) + 安全参数校验码长度(SecurityParameters.mac_length)
2.2.3.2 CBC Block Cipher(密文分组链接分组密码)
CBC模式的全称是Cipher Block Chaining模式(密文分组链接模式)
分组密码(3DES、AES、SM4)使用加密和MAC函数将TLS压缩片段(TLSCompressed.fragment)结构转换为TLS密文片段(TLSCiphertext.fragment)的块(block)结构,如下:
struct {
opaque IV [SecurityParameters.record_iv_length];----通用分组加密(GenericBlockCipher)中传输的初始化向量,必须随机生成
block-ciphered struct { ---分组加密结构
opaque content [TLSCompressed.length]; ---TLS压缩(TLSCompressed)长度
opaque MAC [SecurityParameters.mac_length];---IV的长度,其正确值与使用的密码套件相关
uint8 padding[GenericBlockCipher.padding_length];---填充的数据,详见①
uint8 padding_length;---填充长度,该长度不包括填充长度本身
};
} 通用分组加密(GenericBlockCipher);
①padding:填充的数据,在数据加密前需要将数据填充为密码算法分组长度的整数倍,填充的长度不能超过255个字节。填充的每个字节的内容是填充的字节数。接收者应检查这个填充,如果出错,发送无效的记录MAC(bad_record_mac)报警消息。
组:对于CBC模式下的块密码(密码块链),在传输任何密文之前,了解记录的整个明文是至关重要的。否则,攻击者有可能发起[CCBATT]中描述的攻击。Canvel等人[CCBCTIME]已经证明了基于计算MAC所需时间的CBC填充的定时攻击。为了抵御这种攻击,实现必须确保记录处理时间基本相同,无论填充是否正确。通常,最好的方法是计算MAC,即使填充不正确,然后才拒绝数据包。例如,如果填充(pad)看起来不正确,则实现可能假设为零长度的填充,然后计算MAC。这留下了一个小的定时信道,因为MAC性能在某种程度上取决于数据片段的大小,但由于现有MAC的大块大小和定时信号的小大小,它被认为不够大而不可利用。
2.2.3.3 AEAD Ciphers(关联数据的身份验证加密密码)
关联数据的身份验证加密密码(AEAD Cipher)(如CCM或GCM)函数将TLS压缩片段(TLSCompressed.fragment)结构转换为TLS密文片段(TLSCiphertext.fragment)的AEAD(SecurityParameters.mac_algorithm)结构,如下:
struct {
opaque nonce_explicit [SecurityParameters.record_iv_length];----随机数
aead-ciphered struct { ---AEAD加密结构
opaque content [TLSCompressed.length]; ---TLS压缩(TLSCompressed)长度
};
} 通用AEAD加密(GenericAEADCipher);
AEAD使用密钥(key)、随机数(nonce),明文(plaintext)和"additional data"作为输入进行身份认证。密钥(key)可以是客户端写密钥(client_write_key)或服务端写密钥(server_write_key),没有用到校验码密钥(MAC key)。明文为TLS压缩片段(TLSCompressed.fragment)。额外的认证数据也被称为附加数据(additional_data),定义如下:
additional_data = seq_num + TLSCompressed.type +TLSCompressed.version + TLSCompressed.length
AEAD输出(aead_output)包括AEAD加密操作的密文,其长度通常大于TLS压缩长度(TLSCompressed.length),但会随着AEAD密码(AEAD cipher)变化。由于密码(ciphers)可能会包含填充(padding),数量可能会随着TLS压缩长度的不同而不同。每个AEAD密码的输出不能大于1024字节。
AEADEncrypted = AEAD-Encrypt(write_key, nonce, plaintext,additional_data)
为了加密和验证,AEAD密码(AEAD cipher)会采用密钥(key),随机数(nonce)和"additional data"以及AEAD加密值(AEADEncrypted value)。输出为明文或错误信息,没有完整性校验。如果解密失败,则发出无效的记录MAC(bad_record_mac)报警消息。
TLSCompressed.fragment = AEAD-Decrypt(write_key, nonce,AEADEncrypted,additional_data)
2.3 Key Calculation(密钥计算)
记录层协议使用算法结合握手协议提供的安全参数来生成当前状态(current state)需要的密钥(keys)。主密钥(master secret)被分割为客户端写校验码密钥(client write MAC key)、服务端写校验码密钥(server write MAC key)、客户端写加密密钥(client write encryption key)和服务端写加密密钥(server write encryption key),这四个密钥按照字节顺序分割,未使用的值为空。一些AEAD密码会使用到客户端写初始向量(client write IV)和服务端写初始向量(server write IV)。
当密钥和校验码密钥(MAC keys)生成后,主密钥作为熵。此处使用伪随机函数(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] ----服务端写初始向量
当前客户端写初始向量和服务端写初始向量仅用于AEAD密码
主密钥算法如下:
master_secret = PRF(pre_master_secret, ----预主密钥"master secret",ClientHello.random + ----客户端随机数ServerHello.random ----服务端随机数 )[0..47];
记录层协议使用连接状态(connection state)来维护加密状态,它使用握手协议来获取安全参数并生成相应的密钥,最后对数据进行加密传输。
3 The TLS Handshaking Protocols(TLS握手协议)
TLS有三个子协议用于为记录层提供安全参数,进行相互认证,初始化协商以及报告错误。
握手协议(handshake Protocols)用来协商会话(session),包含以下项目:
- 会话标识符(session identifier):服务端选择的随机序列号,用于标记激活的或是重用的会话
- 对等方证书(peer certificates):对端的X509 v3证书。
- 压缩方法(compression method):加密前使用的压缩算法
- 密码规范(cipher spec):指定用于生成密钥的伪随机函数,块加密算法(null、AES、SM4等)和MAC算法(如HMAC-SHA1、HMAC-SM3)。
- 主密钥(master secret):客户端和服务端的48字节的共享密钥
- 是否重用(is resumable):用于标记该会话是否能被重用
这些参数用来生成安全参数。可以通过握手协议的重用特性,使用相同的会话来初始化链接。
3.1 Change Cipher Spec Protocol(密码规格变更协议)
密码规格变更协议(change cipher spec Protocol)主要用于通知密码规格(cipher spec)的改变,即通知对方使用刚协商好的安全参数来保护接下来的数据。该消息用当前的压缩算法压缩并用当前的密码规格加密,如果是首次协商,该消息为明文。
该消息的长度为一个字节,其值为1。客户端和服务端都要在安全参数协商完毕之后(完成(Finished)消息前)、握手结束消息之前发送此消息。
对于刚协商好的密钥,写密钥在此消息发送之后立即启用;读密钥在收到吃消息之后立即启用。
密码变更消息结构定义如下:
struct {
enum { change_cipher_spec(1), (255) } type;
}ChangeCipherSpec;
客户端或服务端通过发送密码规格变更(ChangeCipherSpec)消息来通知对端后续记录(records)将使用新协商的密码规格和密钥加密保护。接收端接收到该消息后,会通知记录层将读挂起状态(read pending state)中的内容拷贝到读当前状态(read current state)中。在发送完该消息之后,发送端必须将写挂起状态(write pending state)中的内容拷贝到写激活状态(write active state)中。
协商好的安全参数会保存在写挂起状态中,当一端需要使用新的加密算法时,会发送密码规格变更消息,并立即将写挂起状态中的内容覆盖到写当前状态(write current state)中,接收端则修改读状态(read state)的内容。
如果在数据传输过程中出现了重握手,双方仍然可以使用旧的密码规格(CipherSpec)。但是当收到密码规格变更消息时,必须使用新的密码规格。发送密码规格变更的一段并不知道对端是否已经计算出了新的密钥,因此在接收端可能需要一小段时间来缓存接收到的数据。
3.2 Alert Protocol(警报协议)
报警(alert)表达了该消息的严重性并描述了该报警。致命(fatal)级别的报警会导致直接断开链路,在这种情况下,该会话(session)的其他链路可能会继续连接,但该会话编号(session id)会被标记为不可用,用来防止重建链接。跟其他消息一样,报警也会被压缩并加密。
struct {
AlertLevel level;----报警级别,结构详见①
AlertDescription description; ----报警说明,结构详见②
} Alert;
enum {
warning (1), ----警告
fatal (20), ----致命
(255)
} ①报警等级(AlertLevel);
enum{
close_notify (0),----关闭通知
unexpected_message (10), ----意外信息
bad_record_mac (20), ----无效的记录消息校验码
decryption_failed_RESERVED (21), ----解密失败 - 保留字段
record_overflow (22), ----记录溢出
decompression_failure (30), ----解压失败
handshake_failure (40), ----握手失败
no_certificate_RESERVED (41), ----无证书 - 保留字段
bad_certificate (42), ----无效证书
unsupported_certificate (43), ----不支持的证书
certificate_revoked (44), ----证书已撤销
certificate_expired (45), ----证书已过期
certificate_unknown (46), ----未知证书
illegal_parameter (47), ----非法参数
unknown_ca (48), ----未知证书颁发机构
access_denied (49), ----拒绝访问
decode_error (50), ----解码错误
decrypt_error (51), ----解密错误
export_restriction_RESERVED (60), ----导出限制 - 保留字段
protocol_version (70), ----协议版本
insufficient_security (71), ----安全性不足
internal_error (80), ----内部错误
user_canceled (90), ----用户取消
no_renegotiation (100), ----禁止重新协商
unsupported_extension (110), ----不支持的扩展
(255)
} ②报警说明(AlertDescription);
3.2.1 Closure Alerts(关闭报警)
除非出现致命报警,客户端和服务端任何一方在结束连接之前都应发送关闭通知信息。对于该消息的发出方,该消息通知对方不再发送任何数据,可以等待接收方回应的关闭通知消息后关闭本次连接,也可以立即关闭本次连接。对于接收方,收到该消息后应回应一个关闭通知消息,任何关闭本次连接,不再接收和发送数据。
3.2.2. Error Alerts(错误报警)
TLS握手协议处理错误的方式很简单,当一方发现错误时,会将该消息发往对端。当发送或接收该消息时,两端必须立即断开链接,并删除所有与链接相关的会话信息(密钥等)。任何被致命报警(fatal alert)关闭的链接都不能重用。
当遇到无法判定的报警(alert)级别的错误,发送端可能会选择将其作为致命(fatal)级别的错误处理。如果实现中发送报警的目的是为了关闭链接,则必须发送致命级别的报警。
当接收到警告(warning)级别的报警,通常可以继续保持链接。作为发送端,由于通常无法判断接收端接收到警告级别的报警的动作,因此通常警告的报警作用不大。
2.3 Handshake Protocol Overview(握手协议概述)
TLS握手产生会话状态(session state)所需要的加密参数。当客户端和服务端开始通信时,会协商产生版本号,加密算法(可能会进行相互认证),以及使用公钥生成共享密钥。
TLS握手涉及如下过程:
- 交换hello消息来协商密码套件,交换随机数,决定是否会话(session)重用;
- 交换必要的参数,协商预主密钥;
- 交换证书或链间通信协议(IBC),用来验证对方;
- 使用预主密钥(premaster secret)和交换的随机数生成主密钥(master secret);
- 给记录层提供安全参数;
- 验证双方计算的安全参数的一致性、握手过程的真实性和完整性。
需要注意的是上层协议不能过度依赖于TLS可以始终提供健壮的安全链接。实际上有很多中间人攻击可以导致两端切换到它们所支持的最不安全方法。TLS协议用来最小化攻击,但实际中存在很多可能的攻击方式,例如:攻击者可以阻止对运行安全服务的端口的访问,或者试图让对等方协商未经身份验证的连接。使用TLS的基本原则是上层协议必须知道所需要的安全需求,并且不能在低于安全需求的链路上传输信息。因此在使用满足需求的密码套件(cipher suite)前提下,可以认为链路是安全的。
握手过程如下:客户端发送客户端问候(ClientHello)消息给服务端,服务端应回应服务端问候(ServerHello)消息,否则产生一个致命错误并且断开连接。客户端问候(ClientHello)和服务端问候(ServerHello)用于在客户端和服务端进行基于 RSA、ECC或IBC的密码算法协商,以及确定安全传输能力,包括协议版本、会话标识、密码套件等属性,并且产生和交换随机数。
实际交换密钥(key)需要用到4个消息:服务端证书(server Certificate)、服务端密钥交换(ServerKeyExchange)、客户端证书(client Certificate)、客户端密钥交换(ClientKeyExchange)。可以通过定义这些消息的格式以及消息的用途来实现交换密钥的方法,最终客户端和服务端协商出共享密钥。该密钥必须足够长,当前的密钥交换方式交换的密钥长度在46字节以上。
在服务端发送完hello消息之后,接着发送自己的证书消息、服务端密钥交换消息。如果服务端需要验证客户端的身份,则向客户端发送证书请求(CertificateRequest)消息。然后发送服务端 hello完成消息,表示hello消息阶段已经结束,服务端等待客户端的返回消息。如果服务端发送了一个证书请求消息,客户端必须返回一个证书消息。然后客户端发送密钥交换消息,消息内容取决于客户端问候(ClientHello)消息和服务端问候(ServerHello)消息协商出的密钥交换算法。如果客户端发送了证书消息,那么也应发送一个带数字签名的证书验证消息供服务端验证客户端的身份。
接着客户端发送密码规格变更(ChangeCipherSpec)消息,此时客户端会将挂起状态(pending state)中的密码规范(Cipher Spec)拷贝到当前状态(Current state)中,然后客户端立即使用刚协商的算法和密钥(keys & secrets),加密并发送握手结束(Finished)消息。服务端则回应密码规格变更消息,使用刚协商的算法和密钥,加密并发送握手结束消息。至此握手过程结束,服务端和客户端可以开始数据安全传输。应用数据不能先于首次握手(非TLS_NULL_WITH_NULL_NULL的密码套件)发送。
Client ServerClientHello -------->ServerHelloCertificate*ServerKeyExchange*CertificateRequest*<-------- ServerHelloDoneCertificate*ClientKeyExchangeCertificateVerify*[ChangeCipherSpec]Finished -------->[ChangeCipherSpec]<-------- FinishedApplication Data <-------> Application Data* 表示可选或依赖于上下文关系的消息,不是每次都发送 []不属于握手协议消息
服务端密钥交换是独立的TLS消息,不属于握手消息的一部分
如果客户端和服务端决定重用之前的会话,可不必重新协商安全参数。客户端发送客户端问候(ClientHello)消息,并且带上要重用的会话标识(Session ID)。如果服务端有匹配的会话存在,服务端则使用相应的会话状态接受连接,发送一个具有相同会话标识的服务端问候(ServerHello)消息。然后客户端和服务端各自发送密码规格变更消息和握手结束消息。至此握手过程结束,服务端和客户端可以开始数据安全传输。如果服务端没有匹配的会话标识,服务端会生成一个新的会话标识进行一个完整的握手过程。
Client ServerClientHello -------->ServerHello[ChangeCipherSpec]<-------- Finished[ChangeCipherSpec]Finished -------->Application Data <-------> Application Data
2.4 Handshake Protocol(握手协议)
TLS握手协议属于TLS握手协议的上层协议。该协议用于协商会话(session)的安全属性。握手(Handshake)消息在TLS记录(record)层之下运行,被封装成一个或多个TLS明文(TLSPlaintext)结构,并被当前会话的状态(state)处理。
握手协议消息的发送顺序必须遵从如下规则:乱序的握手消息会导致致命(fatal)错误;不需要的握手消息可以被忽略;唯一一个不需要受发送顺序限制的消息是问候请求(HelloRequest),但客户端端进行握手过程中接收到后应该忽略该消息。
2.4.1 Hello Messages(问候消息)
问候(hello)阶段用来建立安全加密的能力。当新建一个会话(session)的时候,记录(record)层连接状态(connection state)的加密,哈希和压缩算法都初始化为空(null)。
2.4.1.1. Hello Request(问候请求)
服务端可以在任何时候发送(问候请求)HelloReques消息。
该消息用来通知客户端进行协商,客户端会发送客户端问候(ClientHello)消息进行响应。该消息不能用于判定哪一端开始建立链接(仅用于初始化协商)。如果客户端正在握手协商中或者客户端不同意建立链接,客户端会发送禁止重新协商(no_renegotiation)警告(alert)。如果服务端发送问候请求(HelloRequest)并没有受到任何响应,服务端可能会关闭链接并发送致命报警(fatal alert)。
此消息不得包含在整个握手过程中维护的消息哈希中,并在完成(finished)消息和证书验证(certificate verify)消息中使用。
2.4.1.2 Client Hello(客户端问候)
客户端发送客户端问候(ClientHello)消息来初始化握手协商,ClientHello也用于响应服务端发送的问候请求(HelloRequest)消息。
struct {
ProtocolVersion client_version;----客户端在这个会话中使用的协议版本,详见①
Random random;----客户端产生的随机信息,结构详见②
SessionID session_id;----客户端在连接中使用的会话标识,定义为:opaque SessionID<0..32>,详见③
CipherSuite cipher_suites<-2>;----密码套件列表,定义为:unit8 CipherSuite[8],详见④
CompressionMethod compression_methods< -1>;----压缩算法列表,定义为:enum{null(0), (255)}CompressionMethod
select(extensions_present) { ---客户端可能会通过扩展字段来请求服务端的扩展功能
case false:
struct { };
case true:
Extension extensions<-1>;---扩展功能,详见⑤
};
} 客户端问候(ClientHello);
①client_version:客户端建议的最低版本且服务端支持的最高版本。TLS 1.2中如果服务端不支持该版本,会返回带有低版本的ServerHello,如果客户端同意使用该低版本,则使用该版本进行协商,否则客户端返回协议版本报警(protocol_version alert)并关闭链接。如果服务端接收到的ClientHello携带了高于服务端支持的最高版本的版本,则必须返回服务端所支持的最高版;当服务端接收的ClientHello中的版本低于服务端所支持的最高版本,如果服务端同意使用该低版本,则会选择不高于ClientHello.client_version中定义的最高版本(如服务端支持TLS1.0、TLS1.1、TLS1.2,client_version为TLS 1.0,则服务端会使用TLS1.0处理),反之返回协议版本报警(protocol_version alert)。
②客户端问候(ClientHello)包含一个随机数的结构体。如下:
struct {
uint32 gmt_unix_time; ----标准unix 32位格式的发送端的内部时钟时间,未规定该时钟的精度。
opaque random_bytes[28];----生成的28字节的安全随机数,随机数并不是会话标识(session ID)
} 随机数(Random);
③session_id:session_id是一个可变长字段,其值有服务端决定。如果没有可以重用的会话标识或希望协商安全参数,该字段应为空。如果该字段非空,该值表示了服务端需要重用的会话,此时会话标识可以来自于①先前的连接标识,②当前的连接标识,③其他处于连接状态的连接标识。第2种用于当客户端仅需要更新随机数的场景;第3种用于建立独立的安全链接(而无需进行完整的握手过程)。当握手协商通过交换完成(Finished)后保存的会话标识超时,或遇到致命(fatal)错误时,会话标识会变为无效状态。会话标识的实际内容由服务端定义。会话标识生成后应一致保持到被超时删除或与这个会话相关的连接遇到致命错误被关闭。一个会话失效或被关闭时则与其相关的连接都应被强制关闭。
注:由于会话标识的传输没有经过加密或MAC保护(因为此时加密参数还未协商出来),服务端端不能在会话标识中放置敏感字段,否则可能导致安全攻击。在握手结束后的消息(含完成(Finished)消息)中的会话标识会被加密保护。
④cipher_suites:客户端问候(ClientHello)中的密码套件(cipher suite)列表给出了客户端支持的加密算法,并按照客户端偏好排序。每个密码套件包含一个密钥(key)交换算法、一个加密算法、一个校验(MAC)算法和PRF。服务端会选择一个合适的密码套件,如果没有合适的则会发送握手失败警告(handshake failure alert)并关闭链接。如果密码套件中包含服务端无法识别的项,服务端必须忽略这些密码套件,并处理剩余的密码套件。
⑤extensions:ClientHello通过判断在compression_method之后是否有多余的字节来确定是否由存在扩展字段。当客户端使用扩展请求额外的功能,但服务端不支持时,客户端可能会断开握手。服务端必须接收带或不带扩展字段的ClientHello,并解析这些扩展字段,如果发现无法解析的字段,则必须返回解码错误警报(decode_error alert)。
客户端发送完ClientHello后会等待服务端的ServerHello,非ServerHello(HelloRequest除外)的消息会被认为致命(fatal)错误。
2.4.1.3 Server Hello(服务端问候)
在服务端接收到客户端问候(ClientHello)且能够接受其中列出的cipher suite时会发送ServerHello
struct {
ProtocolVersion server_version;----服务端在这个会话中使用的协议版本
Random random;----服务端产生的随机信息,结构与客户端随机数相同
SessionID session_id;
CipherSuite cipher_suites;
CompressionMethod compression_methods< -1>;
select(extensions_present) {
case false:
struct { };
case true:
Extension extensions<-1>;
};
} 服务端问候(ServerHello);
注:服务端可能会发送空的会话标识(session id),表示会话(session)不会被缓存且不会被重用。
2.4.1.4 Hello Extensions(问候扩展)
扩展字段的格式如下:
struct {
ExtensionType extension_type;----特定的扩展类型,
opaque extension_data<0..-1>;----特定的扩展的信息
}扩展(Extension);
enum {
signature_algorithms(13),(65535)
}扩展类型(ExtensionType);
IANA维护的扩展参见Section 12
只有在ClientHello中出现的扩展才能出现在ServerHello中。如果客户端接收到的ServerHello中出现了ClientHello中不相关的扩展,则客户端必须断开链接并发送不支持的扩展致命警告(unsupported_extension fatal alert)。未来可能会实现面向服务端(server-oriented)的扩展,参见Hello Extensions
当ClientHello或ServerHello消息中存在多个扩展时,扩展的可以以任意顺序存在,但不能出现同一扩展类型的多个实例。
扩展可以在初始化新的会话(session)或重用会话时发送。在客户端请求重用会话时,由于它并不知道服务端是否会支持不同的扩展,这种情况下,客户端会发送之前发送过的扩展。
每个扩展类型需要规定在完整的握手和会话重用场景下的作用。目前大部分TLS 扩展的实现中仅关注会话初始化:当重用先前会话的时候,服务端不会处理ClientHello中的扩展,也不会将他们保存在ServerHello中。
开发extension的注意点参见Hello Extensions
2.4.1.4.1 Signature Algorithms(签名算法)
客户端使用签名算法(signature_algorithm)扩展来告诉服务端用于数字签名时使用的签名/哈希(signature/hash)算法对。该扩展的扩展数据(extension_data)字段包含支持的签名算法(supported_signature_algorithms)值。
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>;----支持的签名算法
每个签名和哈希算法(SignatureAndHashAlgorithm)列表包含一个签名/哈希(signature/hash)对,按照偏好排序。
注:不是所有的哈希(Hash)和签名(Signature)都能配对
由于密码套件(Cipher Suite)中也定义了允许的签名算法,但没有定义哈希算法,因此在结合该扩展使用时会变得比较复杂,参见2.4.2和2.4.3章节
如果客户端仅支持默认的哈希和签名算法,则可能忽略签名算法(signature_algorithm)扩展。如果客户端不支持默认算法,或支持其他签名/哈希(signature/hash)算法,则必须发送签名算法扩展并列出接受的算法。
如果客户端没有发送签名算法扩展,则服务端必须:
- 如果协商的密钥交换算法为RSA,DHE_RSA,DH_RSA,RSA_PSK,ECDH_RSA,ECDHE_RSA其中之一,则认为客户端发送了{ sha1,rsa }
- 如果协商的密钥交换算法为DHE_DSS,DH_DSS其中之一,则认为客户端发送了{ sha1,dsa }
- 如果协商的密钥交换算法为ECDH_ECDSA,ECDHE_ECDSA其中之一,则认为客户端发送了{ sha1,ecdsa }
TLS1.2之前的版本无法识别该扩展。
服务端不能发送该扩展,但服务端必须支持接收该扩展。
当重用会话(session)时,ServerHello中不能包含该扩展,且server忽略ClientHello中的该扩展。
2.4.2 Server Certificate(服务端证书)
当两端需要使用基于证书的认证时,服务端端必须发送证书(Certificate)消息。该消息总是紧跟在ServerHello消息之后。当选中的密码套件使用RSA或ECC(基于椭圆曲线数学的公开密钥加密算法)或ECDHE(短暂椭圆曲线迪菲-赫尔曼算法)算法时,本消息的内容为服务端的签名证书和加密证书;当选中的密码套件使用IBC(基于标识的密码)或IBSDH(基于标识的超奇异迪菲-赫尔曼算法)算法时,本消息的内容为服务端表示和IBC公共参数,用于客户端和服务端协商IBC公共参数。
证书消息会发送服务端的证书链(certificate chain)。证书必须符合协商出来的密钥交换算法和扩展。
证书消息结构如下:
opaque ASN.1Cert<1..-1>;
struct {
ASN.1Cert certificate_list<0..-1>;----证书序列(链),签名证书在前,加密证书在后详见①
}数字证书(Certificate);
opaque ASN.1IBCParam<1..-1>;
struct {
opaque ibc_id<0..-1>;----服务端标识
ASN.1IBCParam ibc_parameter;----IBC公共参数,遵循ASN.1编码
}数字证书(Certificate);
①certificate_list:这是一个证书序列(链)。发件人的证书必须位于列表的第一位。以下每个证书都必须直接验证其前面的证书。因为证书验证要求独立分发根密钥(root keys),因此在假设远程端必须已经拥有该证书才能在任何情况下对其进行验证的情况下,指定根证书颁发机构(root certificate authority)的自签名证书可能会从链中省略。
客户端响应证书请求(certificate request)消息的消息类型和结构与服务端相同。当客户端没有合适的证书时,可能不会发送证书。
证书必须是X509v3,除非明确协商使用其他类型的证书;证书类型必须能适用于已经确定的密钥交换算法。密钥交换算法和证书密钥类型的关系如下所示:
TLS1.2密钥交换算法与证书密钥类型关系表密钥交换算法 | 证书key类型
--------------------|-------------------------------------------------------------------------------------------------
RSA/RSA_PSK | RSA公钥,证书必须能够用于加密(当出现密钥使用(key esage)扩展时,则必须设置密钥加密(keyEncipherment)位)
DHE_RSA/ECDHE_RSA | RSA公钥,证书必须能够用于签名(当出现密钥使用(key esage)扩展时,则必须设置数字签名(digitalSignature)位)
DHE_DSS | DSA公钥,证书必须能够用于签名(结合服务端密钥交换(server key exchange)消息中出现的哈希算法)
DH_DSS/DH_RSA | Diffie-Hellman公钥,(当出现密钥使用(key esage)扩展时,则必须设置密钥协议(keyAggrement)位)
ECDH_ECDSA/ECDH_RSA | ECDH-capable公钥,该公钥必须使用客户端支持的曲线(curve)和点(point)格式
ECDHE_ECDSA | ECDSA-capable公钥,证书必须允许密钥用于使用将在服务端密钥交换(server key exchange)消息中使用的哈希| 算法进行签名。该公钥必须使用客户端支持的曲线(curve)和点(point)格式 GMTLS密钥交换算法与证书密钥类型关系表密钥交换算法 | 证书key类型
-----------------------|------------------------------------------RSA | RSA公钥,必须使用加密证书中的公钥IBC | 服务端标识和IBC公共参数IBSDH | 服务端标识和IBC公共参数ECC | ECC公钥,必须使用加密证书中的公钥ECDHE | ECC公钥,必须使用加密证书中的公钥
注:ECDH(Elliptic Curve Diffie-Hellman,具备椭圆曲线迪菲-赫尔曼);ECDSA(Elliptic Curve Digital Signature Algorithm,椭圆曲线数字签名算法)
server_name和trusted_ca_keys扩展用于指导证书选择。
如果客户端提供了签名算法(signature_algorithm)扩展,那么服务端提供的所有证书都必须使用该扩展中出现的哈希/签名(hash/signature)算法对进行数字签名,请注意,这意味着可以使用不同的签名算法(例如,使用DSA密钥签名的RSA密钥)对包含一个签名算法的密钥的证书进行签名。这也是与TLS1.1不同的地方,后者要求算法相同。注意,这也意味着DH_DSS、DH_RSA、ECDH_ECDSA和ECDH_RSA密钥交换算法不限制用于签署证书的算法。固定DH证书可以使用扩展中出现的任何哈希/签名算法对进行签名。
如果服务端拥有多个证书,客户端会根据上述标准(以及其他标准,如传输层端点、本地配置和首选项等)挑选其中一个进行验证;如果服务端只有一个证书,则必须满足所有标准。
注:有些证书使用当前无法与TLS一起使用的算法和/或(and/or)算法组合。例如,不能使用具有RSASSA-PSS签名密钥的证书(SubjectPublicKeyInfo中的id-RSASSA-PSS OID),因为TLS没有定义相应的签名算法。
由于为TLS协议指定了指定新密钥交换方法的密码套件,因此它们将包含证书格式和所需的编码密钥信息。
2.4.3 Server Key Exchange Message(服务器密钥交换消息)
该消息会在服务端证书(server certificate)之后发送,且仅在服务端证书消息不足以提供用于客户端交换预主密钥(premaster secret)的数据时,服务端才会发送服务端密钥交换(ServerKeyExchange)消息。密钥交换算法会发送该消息:DHE_DSS、DHE_RSA、DH_anon
以下密钥交换方法发送服务端密钥交换消息是不合法的:RSA、DH_DSS、DH_RSA
此消息传递密码信息,以允许客户端传递预主密钥:迪菲-赫尔曼(Diffie-Hellman)公钥,客户端可以使用该公钥完成密钥交换(结果是预主密钥)或其他算法的公钥。
TLS 1.2 服务端密钥交换消息结构如下:
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..-1>;----用于迪菲-赫尔曼运算的素数模
opaque dh_g <1..-1>;----用于迪菲-赫尔曼操作的生成器
opaque dh_Ys <1..-1>;----服务端迪菲-赫尔曼公共值(mod p)
} 服务端DH参数(ServerDHParams);----短暂迪菲-赫尔曼参数(Ephemeral DH parameters)
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 {};
};
} 服务端密钥交换(ServerKeyExchange);
注:signed_params的签名内容包含客户端和服务端的随机数以及交换算法的参数。
如果客户端提供了签名算法(signature_algorithm)扩展,则该消息中必须使用客户端的签名算法扩展中出现的哈希/签名(hash/signature)算法对。但在实际使用中会出现不一致的情况,如客户端提供了DHE_DSS密钥交换但在签名算法扩展中却忽略所有的DSA(即扩展中忽略了密码套件(cipher suite)中定义的签名算法)。为了能够正确地协商,服务端必须在选择前校验所有的密码套件与签名算法扩展(即密码套件中的签名算法与扩展不一致时)。除此之外,哈希/签名算法必须与服务端证书中的密钥(key)相匹配。RSA密钥可能与任何哈希算法兼容(签名与哈希组合的方式受限于证书)。
GMTLS 服务端密钥交换消息结构如下:
enum {
ECDHE,ECC,IBSDH,IBC,RSA
} 密钥交换算法(KeyExchangeAlgorithm);
struct {
select(KeyExchangeAlgorithm) {
case ECDHE:
ServerECDHEParams params;---服务端的密钥交换参数,详见①
digitally-signed struct {
opaque client_random [32];
opaque server_random [32];
ServerECDHEParams params;
} signed_params;
case ECC:
digitally-signed struct {
opaque client_random [32];
opaque server_random [32];
opaque ASN.1Cert<1..-1>;
} signed_params;
case IBSDH:
ServerIBSDHParams params;---服务端的密钥交换参数,密钥交换参数格式参见SM9算法
digitally-signed struct {
opaque client_random [32];
opaque server_random [32];
ServerIBSDHParams params;
} signed_params;
case IBC:
ServerIBCParams params;---服务端的密钥交换参数,密钥交换参数格式参见SM9算法
digitally-signed struct {
opaque client_random [32];
opaque server_random [32];
ServerIBCParams params;
opaque IBCEncryptionKey[1024];---服务端的加密公钥,长度为1024字节
} signed_params;
case RSA:
digitally-signed struct {
opaque client_random [32];
opaque server_random [32];
opaque ASN.1Cert<1..-1>;
} signed_params;---详见②
};
} 服务端密钥交换(ServerKeyExchange);
①ServerECDHEParams:服务端的密钥交换参数,当使用SM2算法时,交换的参数见GM/T 0009,其中服务端的公钥不需要交换,客户端直接从服务端的加密证书中获取。
②签名参数(signed_params):当密钥交换方法为ECDHE、IBSDH和IBC时,签名参数是服务端对双方随机数和服务端密钥交换参数的签名;当密钥交换方法为ECC和RSA时,signed_params是服务端对双方随机数和服务端加密证书的签名。
2.4.4 Certificate Request(证书请求)
当非匿名服务端需要对客户端进行认证,则应发送此消息,要求客户端发送自己的证书。
该消息紧跟在服务端密钥交换(ServerKeyExchange)之后。
enum {
rsa_sign(1),----包含RSA密钥的证书
dss_sign(2),----包含DSA密钥的证书
rsa_fixed_dh(3),----包含static DH密钥的证书,详见①
dss_fixed_dh(4),----包含static DH密钥的证书,详见①
rsa_ephemeral_dh_RESERVED(5),
dss_ephemeral_dh_RESERVED(6),
fortezza_dms_RESERVED(20),
ecdsa_sign(64),
ibc_params(80),(255)
} 客户端证书类型(ClientCertificateType);
opaque DistinguishedName<1..-1>;
struct {
ClientCertificateType certificate_types<1..-1>;----要求客户端提供证书类型的列表
SignatureAndHashAlgorithm supported_signature_algorithms<-1>;
DistinguishedName certificate_authorities<0..-1>;
} 证书请求(CertificateRequest);
①静态DH算法(static DH):静态DH算法是一种有一方的私钥是静态的,也就是每次密钥协商的时候有一方的私钥都是一样的密钥交换算法。这种算法通常用于服务器方固定,而客户端的私钥则是随机生成的。在静态DH算法中,DH交换密钥只有客户端的公钥是变化的,而服务端公钥是不变的。然而,这种方法已经被废弃了,因为它不具备前向安全性。随着时间延长,黑客可能会截获海量的密钥协商过程的数据,并利用这些数据暴力破解出服务器的私钥,从而计算出会话密钥,导致之前截获的加密数据被破解。
支持的签名算法(supported_signature_algorithms):服务端支持的哈希/签名(hash/signature)算法,按偏好排序。
证书认证机构(certificate_authorities,即CA):可接受的CA的可分辨名称(distinguished names,即DN),DER编码格式。这些DN字段可能会指定一个根CA(root CA)或从属CA(subordinate CA)期望的DN,因此该消息可以用来描述已知的根以及期望的授权范围。如果证书认证机构(certificate_authorities)列表为空,则客户端可能会选择发送合适的客户端证书类型(ClientCertificateType)类型的证书。
证书类型(certificate_types)和支持的签名算法(supported_signature_algorithms)字段交互比较复杂,证书类型在SSLv3版本中出现,但仍然待确定,它的大部分功能被supported_signature_algorithms取代。规则如下:
客户端提供的证书必须使用支持的签名算法中定义的哈希/签名(hash/signature)算法进行签名
客户端提供的证书必须包含于证书类型兼容的密钥(key),如果密钥是一个签名密钥,则该密钥必须能配合支持的签名算法中的哈希/签名算法使用。
由于历史原因,一些客户端证书类型包含用于签名这些证书的算法。如在早期的TLS版本中,rsa_fixed_dh表示使用RSA签名的证书且该证书包含一个static DH密钥。在TLS 1.2中,该功能被支持的签名算法(supported_signature_algorithms)废弃,证书类型不再限制用于签名证书的算法。如:服务端发送的dss_fixed_dh类型的证书,以及{{sha1,dsa},{sha1,rsa}}的签名类型,客户端可能会回复带DH密钥的证书,使用RSA-SHA1签名。
2.4.5 Server Hello Done(服务端问候结束)
服务端发送该消息用来表示完成了密钥交换(key exchange)消息的交互,客户端可以进入密钥交换阶段。客户端在接收到服务端问候结束(ServerHelloDone)消息后,会对服务端提供的证书进行校验,如果证书校验通过则继续对服务端的问候(Hello)参数进行校验。
2.4.6 Client Certificate(客户端证书)
该消息是客户端接收到服务端问候结束(ServerHelloDone)消息之后发送的第一个消息,且只在服务端请求证书时发送。如果没有合适的证书,客户端也必须发送该消息,消息中不包含任何证书,即证书列表(certificate_list)长度为0。如果客户端没有发送任何证书,服务端可能会握手(不对客户端进行认证),也可能会返回握手失败致命报警(handshake_faiure fatal alert)。如果证书链的某些地方不可接受(如签名的CA不可知),服务端可能会继续握手或返回致命报警(fatal alert)。
该消息会传递客户端的证书链,服务端会使用该消息来校验证书认证(CertificateVertify)消息或采用非短暂迪菲-赫尔曼算法(non-ephemeral Diffie-Hellman)计算预主密钥(premaster secret)(non-ephemeral Diffie-Hellman)。证书必须匹配协商的密码套件(cipher suite)的密钥交换算法以及扩展。
证书类型必须是X509v3(除非明确指出使用其他类型的证书)。
客户端证书必须于证书请求(CertificateRequest)中的证书类型匹配。
TLS1.2密钥交换算法与证书密钥类型关系表密钥交换算法 | 证书key类型
--------------------------|-------------------------------------------------------------------------------------------------
rsa_sign | RSA公钥,证书必须能够用于签名(结合证书验证(certificate verify)消息中的哈希签名(hashsignature)算法)
dss_sign | DSA公钥,证书必须能够用于签名(结合证书验证(certificate verify)消息中的哈希签名(hashsignature)算法)
ecdsa_sign | ECDSA-capable公钥,证书必须能够用于签名(结合证书验证(certificate verify)消息中的哈希签名| (hashSignature)算法);公钥必须使用服务端支持的曲线(curve)和点(point)格式
rsa_fixed_dh/dss_fixed_dh | Diffie_Hellman公钥,必须与服务端密钥(server key)的预主密钥(premaster)相同
ECDH_ECDSA/ECDH_RSA | ECDH-capable公钥,该公钥必须使用客户端支持的曲线(curve)和点(point)格式
rsa_fixed_ecdh/ | ECDH-capable公钥,必须与服务端密钥(server key)的曲线(curve)相同,必须使用服务端支持的点(point)格式
ecdsa_fixed_ecdh | GMTLS密钥交换算法与证书密钥类型关系表密钥交换算法 | 证书key类型
-----------------------|------------------------------------------RSA | RSA公钥,必须使用加密证书中的公钥IBC | 服务端标识和IBC公共参数IBSDH | 服务端标识和IBC公共参数ECC | ECC公钥,必须使用加密证书中的公钥ECDHE | ECC公钥,必须使用加密证书中的公钥
如果证书请求(Certificate Request)消息中的证书认证机构(certificate_authorities)列表不为空,则证书链中的一个证书应由其中一个列出的CA颁发。
证书必须使用可接受的哈希/签名算法对(hash/signature algorithm pair)进行签名,这放宽了以前版本的TLS中对证书签名算法的限制。
2.4.7 Client Key Exchange Message(客户端密钥交换消息)
如果服务端请求客户端证书,本消息紧跟于客户端证书(Client Certificate)消息之后,否则本消息是客户端己收到服务端问候完成(Server Hello Done)消息后所发送的第一条消息。
发送该消息表示预主密钥(premaster secret)已经生成,通过RSA加密后直接传输或通过Diffie-Hellman参数的传输来设置预主秘密,该参数将允许每一方同意相同的预主秘密。
如果密钥交换算法使用RSA算法、ECC算法和IBC算法,本消息中包含预主密钥,该预主密钥由客户端产生,采用服务端的加密公钥进行加密。当服务端收到加密后的预主密钥后,利用相应的私钥进行解密,获取所述预主密钥的明文。如果是IBC算法,客户端利用获取的服务端标识和IBC公开参数,产生服务端公钥。如果是RSA算法,建议使用PKCS#1 版本1.5对RSA加密后的密文进行编码。若甫哦密钥交换算法使用ECDHE算法或IBSDH算法,本消息中包含计算预主密钥的客户端密钥交换参数。
当客户端使用短暂的Diffie-Hellman指数时,则此消息包含客户端的Diffie-Hellman公共值。如果客户端正在发送包含静态DH指数(static DH exponent)的证书,即它正在进行fixed_DH客户端身份验证,则此消息必须发送,但必须为空。
struct {
select(KeyExchangeAlgorithm) {
case rsa:
EncryptedPreMasterSecret;----加密的预主密钥
case dhe_dss:
case dhe_rsa:
case dh_dss:
case dh_rsa:
case dh_anon:
ClientDiffieHellmanPublic;----客户端的Diffie-Hellman公钥
}exchange_keys;
} 客户端密钥交换(ClientKeyExchange);
GMTLS 服务端密钥交换消息结构如下:
struct {
select(KeyExchangeAlgorithm) {
case ECDHE:
Opaque ClientECDHEParams<1..-1>;----客户端ECDHE参数,详见①
case IBSDH:
Opaque ClientIBSDHParams<1..-1>;----使用IBSDH算法时,客户端的密钥交换参数
case ECC:
opaque ECCEncryptedPreMasterSecret<0..-1>;----使用ECC加密算法时,用服务端加密公钥加密的预主密钥
case IBC:
opaque IBCEncryptedPreMasterSecret<0..-1>;----使用IBC加密算法时,用服务端加密公钥加密的预主密钥
case RSA:
opaque RSAEncryptedPreMasterSecret<0..-1>;----RSA加密的预主密钥,详见②
}exchange_keys;
} 服务端密钥交换(ServerKeyExchange);
①ClientECDHEParams:使用ECDHE算法时,要求客户端发送证书。客户端的密钥交换参数,当使用SM2算法时,交换的参数见GM/T 0009。其结构如下:
struct {
ECParameters curve_params;----EC参数
ECPoint public;----EC点
} 客户端ECDHE参数(ClientECDHEParams);
如果使用SM2算法,第一个参数不校验。
②RSAEncryptedPreMasterSecret:使用RSA加密算法时,用服务端加密公钥加密的预主密钥。预主密钥的数据结构如下:
struct {
ProtocolVersion client_version;----客户端所支持的版本号。服务端要检查这个值是否与客户端问候(ClientHello)中所发送的值相匹配
opaque random[46];----46字节的随机数
} 预主密钥(PreMasterSecret);
2.4.7.1 RSA-Encrypted Premaster Secret Message
RSA用于密钥协商和认证,客户端会生成48字节的预主密钥(premaster secret),并将预主密钥使用服务端证书提供的公钥进行加密,最后发送到服务端。该结构是客户端密钥交换(ClientKeyExchange)的变种,且不是一个独立的消息。
struct {
ProtocolVersion client_version;----客户端所支持的版本号。服务端要检查这个值是否与客户端问候(ClientHello)中所发送的值相匹配
opaque random[46];----46字节的随机数
} 预主密钥(PreMasterSecret);
struct {
public-key-encrypted PreMasterSecret pre_master_secret;----客户端用于生成主密钥(master secret)的随机值,详见①
} 加密预主密钥(EncryptedPreMasterSecret);
①pre_master_secret:预主密钥(PremasterSecret)中的版本号由ClientHello.client_version提供,而非协商的链路版本,该设计用于防止回滚(rollback)攻击。不幸的是有些老的实现会使用协商出的版本号,与这些实现交互时可能会失败。
客户端的实现中必须在预主密钥中发送正确的版本号。如果ClientHello.client_version为TLS1.1或更高,服务端的实现中必须检查该版本号;如果版本号为1.0或更早,服务端可能会检查版本号,也可能不检查。
TLS 服务端在加密预主密钥失败或遇到非期望的版本号时不能发送报警(alert),而应该使用一个随机生成的预主密钥进行握手。
公钥加密的数据是一个0~-1长度的不透明向量(opaque vector),因此客户端密钥交换(ClientKeyExchange)中使用RSA加密的预主密钥(PreMasterSecret)之前为2个字节的长度字段(表示其可变长度大小)。加密预主密钥(EncryptedPreMasterSecret)是客户端密钥交换的唯一数据。
2.4.7.2 Client Diffie-Hellman Public Value(客户端DH公钥值)
当客户端的数字证书(certificate)中没有出现迪菲-赫尔曼公钥(Diffie-Hellman public)的值时发送,该消息是客户端密钥交换的变体。当数字证书中已经包含合适的迪菲-赫尔曼密钥(Diffie-Hellman key),用于固定DH(fixed_dh)客户端认证,此时必须于发送客户端密钥交换(client key exchange)消息,但必须为空。
2.4.8 Certificate Verify(证书验证)
该消息用于提供客户端证书的校验,证明客户端拥有这些证书的私钥。仅在客户端证书(client certificate)由签名功能时发送(除了包含fixed Diffie-Hellman参数的证书),跟在客户端密钥交换(client key exchange)消息之后发送。
TLS1.2 Certificate Verify结构:
struct {
digitally-signed struct {
opaque handshake_messages [handshake_messages_length]
}
} 证书验证(CertificateVerify);
这里的握手消息(handshake_messages)表示从客户端问候(ClientHello)开始的所有接收和发送的握手消息(不包含本消息),该消息级联了所有握手消息,结构如下:
struct {
HandshakeType msg_type;----握手类型(handshake type)
uint24 length; ----消息中的字节
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)为止的所有可能的哈希算法。服务端可以通过在证书请求(CertificateRequest)中设置限制来减小运算。
签名的哈希/签名算法(hash/signature algorithm)必须是证书请求消息中提供的。除此之外,哈希/签名算法必须与客户端端的数字证书兼容。
GMSSL Certificate Verify结构:
struct {
Signature signature ;
} 证书验证(CertificateVerify);
Signature 的结构如下:
enum {
rsa_sha1,rsa_sm3,ecc_sm3 ,ibs_sm3;
} 签名算法(SignatureAlgorithm);
struct {
select(Signature Algorithm){
case rsa_sha1 :
digitally-signed struct {
opaque sha1_hash [20];
};
case rsa_sm3 :
digitally-signed struct {
opaque sm3_hash [32];
};
case ecc_sm3 :
digitally-signed struct {
opaque sm3_hash [32];
};
case ibs_sm3 :
digitally-signed struct {
opaque sm3_hash [32];
};
} body;
} 签名(Signature);
sm3_hash和sha1_hash是指哈希运算的结果,运算的内容是值客户端问候(ClientHello)开始直到本消息为止(不包括本消息)的所有握手有关的消息(加密证书要包在签名计算中),包括握手消息的类型和长度域。
2.4.9 Finished(结束)
服务端和客户端各自在密码规格变更(ChangeCipherSpec)消息之后发送本消息,用于验证密钥交换过程是否成功,并校验握手过程的完整性。
本消息用本次握手过程协商出的算法和密钥保护。
本消息的接收方必须校验消息内容的正确性。一旦一方发送了握手结束消息,并且接收到了对方握手结束消息并通过校验。就可以使用该连接进行数据安全传输。
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),当前版本则却决于使用的密码套件。任何没有明确指定验证数据长度(verify_data_length)的默认为12字节。
1 octet = 8 bit
finished_label:客户端发送的结束消息相关的字符串为“client finished”,服务端发送的为“server finished”
Hash:表示握手消息的哈希,该哈希与伪随机算法(PRF)使用的哈希相同。密码套件定义的PRF必须定义用于结束计算的Hash,若为GMSSL则哈希算法为SM3
handshake_messages:指自客户端问候(Client Hello)开始直到本消息为止(不包括本消息、密码规格变更消息(ChangeCipherSpec))的所有与握手有关的消息,包括握手消息的类型和长度域。不包含记录(Record)类型、问候请求(HelloRequest)的消息。
在握手过程中,如果没有在合适的时机处理结束(Finished)消息,则返回致命(fatal)错误。
A.5 The Cipher Suite(密码套件)
本节定义了客户端问候(ClientHello)和服务端问候(ServerHello)使用的密码套件(cipher suite)。TLS初始化的密码套件为TLS_NULL_WITH_NULL_NULL,但不能使用该值进行协商(没有提供任何保护)
CipherSuite TLS_NULL_WITH_NULL_NULL = { 0x00,0x00 };
如下密码套件需要服务端提供用于密钥交换的RSA证书。服务端可能会在证书请求(CertificateReuqest)中请求具有签名的能力或功能的证书(signature-capable certificate)。
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 };
如下密码套件用于。DH表示服务端的证书包含CA签名的迪菲-赫尔曼算法(Diffie-Hellman)参数。DHE表示短暂迪菲-赫尔曼算法(ephemeral Diffie-Hellman),此时使用具有签名的能力或功能的证书(signature-capable certificate)(该证书也被CA签名)签名迪菲-赫尔曼算法参数。服务端使用的签名算法在密码套件的DHE名字之后。
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 };
服务端可以向客户端请求具有签名的能力或功能的证书(signature-capable certificate)或DH证书来对客户端进行认证。客户端提供的所有Diffie-Hellman证书必须使用服务端提供(或生成)的参数
如下密码套件用于在两端都没有认证情况下的匿名Diffie-Hellman交互。该模式容易遭中间人攻击,因此使用场景比较有限。该模式不能用于TLS1.2的实现中,除非明确指出允许匿名密钥交互。
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 };
目前使用最多的密码套件:TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256解析如下:
ECDHE为密钥交互算法,RSA为签名算法,AES_123_GCM为使用GCM模式下的128位AEAD加密算法,SHA256为创建消息摘要的MAC算法
密码套件的详细定义可以参考Cipher suite definitionsTLS密码套件(cipher suite)-CSDN博客Cipher suite definitions
B. 术语表
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:数字签名使用公钥和单向哈希函数来产生签名数据。
Data Encryption Standard(DES):数据加密标准,普遍使用的对称加密算法
Initialization Vector (IV):初始向量,当块加密使用CBC时,首个文本块会与IV进行异或运算。
client write key:用于加密客户端的数据
client write MAC key:用于认证客户端的数据
Message Authentication Code (MAC):消息认证码,单向哈希处理的数据,生成消息摘要。
master secret:主密钥,用于生成用于数据加密和完整性校验的会话密钥
RC4:流加密。
session:会话定义了可与多个链接共享的安全参数。用来避免为每个链接进行安全参数协商造成的消耗。
stream cipher:流加密,将一个密钥加密为密钥流(keystream),并与文本进行异或运算。
F.1.1.2 RSA Key Exchange and Authentication(RSA密钥交换和身份验证)
使用RSA,密钥交换(key exchange)和服务端认证可以同时进行。服务端的证书中包含了公钥。注意,使用静态的RSA密钥可能导致该静态密钥保护的所有会话(session)失效。
客户端验证完服务端的证书后,会使用证书中的公钥对预主密钥(pre_master_secret)加密。服务端成功解密预主密钥之后会并处理结束(Finished)消息之后,服务端认为客户端对服务端的认证成功。
当使用RSA进行密钥协商时,客户端通过证书验证(certificate verify)消息进行认证。客户端会对所有处理的握手消息进行签名,这些握手消息包括服务端证书(该消息使用的签名与服务端相关)以及服务端问候随机数(ServerHello.random),该数值与当前握手交互使用的签名相关。
F.1.1.3 Diffie-Hellman Key Exchange with Authentication(带身份验证的迪菲-赫尔曼密钥交换)
当使用Diffie-Hellman密钥交换时,服务端可以提供包含固定DH(fixed Diffie-Hellman)参数的证书或使用服务端密钥交换(ServerKeyExchange)消息发送一系列使用DSA或RSA签名的临时DH参数。这些临时参数在签名前会使用问候随机数(hello.random)进行哈希,用以防止攻击者使用老参数进行攻击。其他场景下,客户端可以通过验证证书和签名来确保该消息来自服务端。
如果客户端有包含固定DH(fixed Diffie-Hellman)参数的证书,该证书中的信息可以用于完成密钥交互。这种情况下,客户端和服务端会生成相同的DH结果,即预主密钥(pre_master_secret)。为了防止预主密钥过长地滞留在内存中,在完成它的计算后应该尽快将其转化为主密钥(master secret)。客户端的DH参数必须兼容服务端的DH参数。
如果客户端使用DSA或RSA证书或其未被认证,它会在客户端密钥交换(clientKeyExchange)消息中发送临时的参数,后续可能使用证书验证(certificate verify)消息对其进行认证。
如果使用DH密钥对进行多个握手,由于客户端和服务端都拥有一个包含固定DH(fixed Diffie-Hellman)密钥对的证书或服务端重用DH密钥,需要注意方式子群攻击(Subgroup attack)。
小型的子群攻击攻击可以通过使用DHE密码套件(DHE cipher suite)并未每个握手生成新的DH私钥来解决。
由于TLS允许服务端提供任意的DH组(DH groups),客户端应该对DH组进行校验(与本地策略比较)。
F.1.3 Detecting Attacks Against the Handshake Protocol(检测针对握手协议的攻击)
攻击者可能会通过握手消息来修改两端使用的加密算法。因此,攻击者必须修改握手过程中的一个或多个消息,当发生这种情况的时候,客户端和服务端会计算出不同的哈希值(消息摘要改变),此时两端不会接受对端的结束(Finished)消息。由于无法获取主密钥(master_secret),攻击者无法修改结束(Finished)消息,这样就可以发现攻击。
F.1.4 Resuming Sessions(重用会话)
当需要重用会话(session)来建立链接时,会使会话的主密钥(master secret)生成新的ClientHello.random和SeverHello.random。
只有在客户端和服务端双方同意的情况下才能重用会话。如果一端拒绝或证书过期(或被撤销),此时应该进行完整的握手。
F.2 Protecting Application Data(保护应用程序数据)
使用使用ClientHello.random和ServerHello.random生成的主密钥(master secret)用于生成用于数据加密和完整性校验的会话密钥
master_secret = PRF(pre_master_secret, "master secret",ClientHello.random + ServerHello.random)
使用MAC保护发送的数据。为了方式消息重复和篡改,使用MAC key,序列号,消息长度,消息内容以及2个固定字符串生成MAC。消息类型字段用来确保该消息服务于某个TLS记录(Record)层。序列号用来确保能够感知到消息的删除和重复。由于序列号为64位长度,可以保证数值不会被溢出。由于使用了独立的消息认证码密钥(MAC key),一方的消息不能插入到另一方的输出中(插入后MAC会错误)。类似地,由于客户端写密钥(clientwrite keys)和服务端写密钥(server write keys)是独立的,因此只会用到一次流加密密钥。
MAC(MAC_write_key, seq_num +TLSCompressed.type +TLSCompressed.version +TLSCompressed.length +TLSCompressed.fragment);
如果攻击者获取了加密密钥,那么所有的加密消息都会被读取。类似地,泄露消息认证码密钥会导致消息篡改(message-modification)攻击。由于MAC是加密的,消息篡改同时也需要突破对MAC的加密算法。
F.4 Security of Composite Cipher Modes(复合密码模式的安全性)
TLS使用密码套件(cipher suite)中定义的对称加密和认证函数对传输的应用数据进行保护,目的是防止网络攻击造成数据的篡改。
当前最健壮的方式称为加密再认证(encrypt-then-authenticate),首先对数据进行加密,然后对密文使用MAC计算消息摘要。该方法使用加密和MAC函数保证数据的加密防护和完整性。前者用于防止针对明文的攻击(信息泄露),后者用于防止针对消息的攻击(链路攻击)。其次,TLS使用其他方式,称为认证再加密(authenticate-then-encrypt),首先先对明文使用MAC,然后对整个数据(明文+MAC)进行加密。这种方式已经通过使用特定加密函数和MAC函数的组合证明了其安全能力,但通常不能保证安全。特别地,在使用当前非常好的加密函数再结合某个MAC函数的情况下,无法很好地防御主动攻击。
目前已经证明在某些情况下更加适合使用认证再加密。其中一种情况是使用流加密时,消息+MAC的长度不可知的情况;另一种时在CBC模式下使用块加密。这些情况下,安全能力可以通过一方对明文+MAC使用CBC加密以及对每个明文和MAC使用(新的,独立的,不可预测的)IV体现出来。
TLS使用块加密+HMAC,流加密+HMAC以及AEAD(Authenticated-Encryption With Additional data,附加数据的身份验证加密)来保证数据的完整和安全。TLS1.3中废除了除AEAD外的加密方式
注:
- 实际中基本不使用流加密方式
- 密钥交换(key exchange)的目的是生成预主密钥(pre_master_secret),最终生成主密钥(master secret),通过结束(Finished)来校验预主密钥的正确性。
- 数字签名过程为发送端首先使用哈希算法生成消息摘要,然后使用签名算法生成数字签名;接收端为上述逆过程
参考:
https://www.cnblogs.com/charlieroro/p/10714783.html
【精选】《商用密码应用与安全性评估》第一章 密码基础知识-小结_商用密码应用安全性评估-CSDN博客
RFC 5246 TLS1.2
中华人民共和国密码行业标准 GM/T 0024-2014 SSL VPN 技术规范
百度百科相关词条
TLS密码套件(cipher suite)-CSDN博客
IBM Documentation