前言
这里来对TLS1.2做一个基本的分析,研究,主要从设计目标,基本架构,核心要素来进行分析,然后协议主要内容是关于Handshake层和Record层。
正文
设计目标
- 安全性:机密性(公钥协商和对称加密),身份认证(例如证书),完整性(例如mac)
- 可拓展性:可以替换密码组件;拥有拓展功能
- 性能:冷热启动,会话恢复
基本架构
分为五种协议,
- handshake:生成对称加密所需的参数,身份认证,会话恢复等功能
- record:利用handshake生成的参数进行分段,压缩,加密,mac,再发送
- alert:通知返回码
- changecipher spec:通知对端协议已经切换,冗余1.3删除
- application data:将上层http等协议数据传入record层做处理
最主要的是handshake和record,然后record层封装其他四个协议
密码学相关
密码组件
主要涉及到三大类的密码组件
- 1.对称加密传输组件,例如aes-128-gcm
- 2.认证密钥协商组件,例如rsa-ecdhe;
- 3.密钥扩展组件,例如TLS-PRF-sha256
这三类组件又可以划分为五种密码相关
- authentication (认证算法)
- encryption (加密算法 )
- message authentication code (消息认证码算法 简称MAC,在aead模式下不要mac)
- key exchange (密钥交换算法)
- key derivation function (密钥衍生算法)
eg: ECDHE-RSA-AES-128-GCM-SHA256
使用ecdhe做handshake时密钥交换,rsa做认证,aes-128-gcm做为record层的加密算法,然后因为gcm是aead模式不需要单独的mac所以没有mac算法,然后sha256作为密钥衍生算法的选择,从handshake协商出来的参数衍生出record所需要的参数
前向安全性
秘钥泄露后对之前的历史信息不会有影响,RSA和静态DH不满足前向安全,DHE和ECDHE支持前向安全
接下来主要说下record层和handshake层
Record层
流程大致是:分段,压缩(可选,安全问题一般不开启),加密和mac(三种模式,stream ,block,aead),添加消息头。
tls机密性,完整性和防重放就是在这一层实现的。
在handshake层会协商后会得到以下参数:
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; /*mac 算法*/
uint8 mac_length; /*mac 值的长度*/
uint8 mac_key_length; /*mac 算法密钥的长度*/
CompressionMethod compression_algorithm;
opaque master_secret[48];
opaque client_random[32];
opaque server_random[32];
} SecurityParameters;
client和server利用上述参数和PRF函数生成下列的加密key,mac key和iv,根据具体算法不同是可选的,注意的是client和server两端的key是不同的,会存在安全问题
client write MAC key*
server write MAC key*
client write encryption key
server write encryption key
client write IV
server write IV
*表示可选,AEAD模式无需MAC
分层和压缩暂且不看,我们重点关注加密和Mac这一步骤
压缩后得到的数据结构是这样的
struct {
ContentType type;
ProtocolVersion version;
uint16 length;
select (SecurityParameters.cipher_type) {
case stream: GenericStreamCipher;
case block: GenericBlockCipher;
case aead: GenericAEADCipher;
} fragment;
} TLSCiphertext;
三种加密mac方式
stream
先计算mac: seq_num防重放,type,version,length,fragment避免数据被篡改
MAC(MAC_write_key, seq_num + TLSCompressed.type + TLSCompressed.version + TLSCompressed.length + TLSCompressed.fragment);
再加密得到的结构为
stream-ciphered struct { opaque content[TLSCompressed.length]; opaque MAC[SecurityParameters.mac_length]; } GenericStreamCipher;
block
先mac
MAC(MAC_write_key, seq_num + TLSCompressed.type + TLSCompressed.version + TLSCompressed.length + TLSCompressed.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;
注意IV向量需要时随机的,否则会有安全问题
AEAD
不用mac,
AEAD 模式 加密 密码套件 GCM AES-128-GCM TLS_DHE_RSA_WITH_AES_128_GCM_SHA256 CCM AES-128-CCM TLS_RSA_WITH_AES_128_CCM ChaCha20-Poly1305 ChaCha20-Poly1305 ECDHE-ECDSA-CHACHA20-POLY1305 输入为秘钥,nonce,明文fragment,additional data
nonce由密码族指定方式生成
additional data为
additional_data = seq_num + TLSCompressed.type + TLSCompressed.version + TLSCompressed.length;
计算过程为
AEADEncrypted = AEAD-Encrypt(write_key, nonce, plaintext, additional_data)
最后的输出为
struct { opaque nonce_explicit[SecurityParameters.record_iv_length]; aead-ciphered struct { opaque content[TLSCompressed.length]; }; } GenericAEADCipher;
以上三种加密和mac后最后添加消息头
ContentType type;
ProtocolVersion version;
uint16 length;
Handshake层
这一层的主要目的是:
- 生成提供给Record层的SecurityParameters
- 身份认证(一般是客户端认证服务器的身份)
然后又分为冷启动,热启动(session_id或者session_ticket)
我们先看下冷启动的流程图:
Client Server
ClientHello -------->
ServerHello
Certificate*
ServerKeyExchange*
CertificateRequest*
<-------- ServerHelloDone
Certificate*
ClientKeyExchange
CertificateVerify*
[ChangeCipherSpec]
Finished -------->
[ChangeCipherSpec]
<-------- Finished
Application Data <-------> Application Data
*表示可选发送
[]表示独立消息
ClientHello:
struct {
uint32 gmt_unix_time;
opaque random_bytes[28];
} Random;
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;
携带版本号,随机数(PRF生成master secret,session key,校验,用于防重放),session_id(会话恢复),cipher_suites(密码组件),压缩方法,以及拓展模块
ServerHello:
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;
确认版本信息,选择密码组件,回复服务端随机值,session_id会话恢复选择,压缩选择,拓展
ServerCertificate
服务端发送证书,证书中会包含公钥,然后公钥又会被上级CA签名认证,用来做身份认证,以及利用服务器公钥传递加密参数
ServerKeyExchange
当证书中的数据不足生成premaster secret时会发送这个信息,例如DHE,ECDHE类的,因为使用的是临时参数
Certificate Request
服务端也可以请求认证客户端
Server Hello Down
表示已经发送完服务端的消息
Client Certificate
假设client收到server certificate request请求后发送一个证书,用于传输证书链给服务端,或者使用静态DH时包含静态DH参数来计算premaster secret
Client Exchange
发送必须的参数来生成premaster secret
- RSA:利用服务器公钥加密一段48(46随机+2client_version)位的值作为premaster secret发送给server
- DHE,ECDHE:发送client生成的动态公钥
- 静态DH:发送个空的
Certificate verify
对client进行显示验证,client用自己的私钥签名一段数据给服务器(消息内容为客户端发送certificate verify前,所有收到和发送的握手信息)
Finished
利用协商出来的参数生成master secret然后结合之前的握手信息,标志做PRF运算得到verify_data来做校验,防止之前的信息被篡改
struct {
opaque verify_data[verify_data_length];
} Finished;
verify_data =
PRF(master_secret, finished_label, Hash(handshake_messages))
[0..verify_data_length-1];
finished_label:
对于由 Client 发送的结束消息,字符串是 "client finished"。 对于由 Server 发送的结束消息,字符串是"server finished"。handshake_messages 的值包括了从 ClientHello 开始一直到(但不包括)本次Finished 消息的所有握手消息
会话恢复
session_id
Client Server
ClientHello -------->
ServerHello
[ChangeCipherSpec]
<-------- Finished
[ChangeCipherSpec]
Finished -------->
Application Data <-------> Application Data
CH中包含一个session_id,然后server检查是否包含并使用,如果使用就在返回的SH中包含相同的session_id,之后就不需要certificate这些步骤了。
- 会话恢复中保存的是master secret,并且会生成新的client random和server random,这样生成的session key就会不同,增强了安全性。
- session_id是明文的,所以不能存储敏感信息
优点:
- 2RTT -> 1RTT
- 减少加密计算
缺点:
- 分布式中的的session存储问题
session_ticket
为了解决分布式session问题,所有的状态都保存在客户端。服务器取出它的所有会话数据(状态)并进行加密 (密钥只有服务器知道),再以票证的方式发回客户端,之后客户端将加密信息返回给服务端,服务端检验完整性,并解密,恢复会话。
生产票据
Client Server
ClientHello
(empty SessionTicket extension)-------->
ServerHello
(empty SessionTicket extension)
Certificate*
ServerKeyExchange*
CertificateRequest*
<-------- ServerHelloDone
Certificate*
ClientKeyExchange
CertificateVerify*
[ChangeCipherSpec]
Finished -------->
NewSessionTicket
[ChangeCipherSpec]
<-------- Finished
Application Data <-------> Application Data
使用:
Client Server
ClientHello
(SessionTicket extension) -------->
ServerHello
(empty SessionTicket extension)
NewSessionTicket
[ChangeCipherSpec]
<-------- Finished
[ChangeCipherSpec]
Finished -------->
Application Data <-------> Application Data
需要支持sessionticket拓展
密钥使用
三个秘钥premaster secret,master secret,session key
利用PRF函数premaster secret生成master secret,master secret生成session key。
premaster secret生成master secret(48位)
master_secret = PRF(pre_master_secret, "master secret",
ClientHello.random + ServerHello.random)
[0..47];
master secret生成session key的函数:
key_block = PRF(SecurityParameters.master_secret,
"key expansion",
SecurityParameters.server_random +
SecurityParameters.client_random);
然后再切分key_block
然后看下PRF,利用了指定的HMAC
P_hash(secret, seed) = HMAC_hash(secret, A(1) + seed) +
HMAC_hash(secret, A(2) + seed) +
HMAC_hash(secret, A(3) + seed) + ...
A(0) = seed
A(i) = HMAC_hash(secret, A(i-1))
所以PRF具体为:
PRF(secret, label, seed) = P_<hash>(secret, label + seed)
总结
整个TLS1.2的大致流程就是这样的,我们可以看出其设计的整体思路围绕着预定目标,很好的展现了密码学与软件工程相结合的设计,是一个值得学习的架构设计。
参考: