前言
2018年底正式发布了TLS1.3,对TLS1.2整体进行了升级,从安全性,性能方面大大提升,但是并没有做完全的前向兼容,并且目前的工业届也在逐渐支持使用TLS1.3,这里就简单的分析学习下。
正文
主要改变
- 秘钥拓展函数从PRF升级为HKDF
- 删除了不安全的密码组件如RC4,MD5等
- 秘钥协商只用ECDHE保证了前向安全
- 提前带上支持的密码组件和参数从而冷启动时2-RTT变为1-RTT
- PSK模式代替session_id和session_ticket
- 基于PSK和early_data的0-RTT模式
- 删除changecipher协议,添加心跳协议
基本架构
Handshake层
Key ^ ClientHello
Exch | + key_share*
| + signature_algorithms*
| + psk_key_exchange_modes*
v + pre_shared_key* -------->
ServerHello ^ Key
+ key_share* | Exch
+ pre_shared_key* v
{EncryptedExtensions} ^ Server
{CertificateRequest*} v Params
{Certificate*} ^
{CertificateVerify*} | Auth
{Finished} v
<-------- [Application Data*]
^ {Certificate*}
Auth | {CertificateVerify*}
v {Finished} -------->
<-------- [NewSessionTicket]
[Application Data] <-------> [Application Data]
+ 表示的是在以前标注的消息中发送的值得注意的扩展
* 表示可选的或者依赖一定条件的消息/扩展,它们不总是发送
() 表示消息由从 Client_early_traffic_secret
导出的密钥保护
{} 表示消息使用从一个 [sender]_handshake_traffic_secret
导出的密钥保护
[] 表示消息使用从 [sender]_application_traffic_secret_N
导出的密钥保护
ClientHello
tls1.3必须在拓展中包含supported_versions并正确设置
tls1.3中新添加了使用supported_groups和key_share来做确认,如果要使用PSK,会有psk_key_exchange_models展示支持的psk模式和pre_shared_key展示拥有的psk
ServerHello
返回选择key_share,如果是PSK模式会恢复pre_shared_key选择,之后的消息都会被加密传输
early secret 和 ecdhe secret 导出 server_handshake_traffic_secret。再从 server_handshake_traffic_secret中导出 key 和 iv,使用该 key 和 iv 对 Server hello 之后的握手消息加密。同样的计算 client_handshake_traffic_secret,使用对应的 key 和 iv 进行解密后续的握手消息。
Early Secret = HKDF-Extract(salt, IKM) = HKDF-Extract(0, PSK) = HKDF-Extract(0, 0)
Handshake Secret = HKDF-Extract(salt, IKM) = HKDF-Extract(Derive-Secret(Early Secret, "derived", ""),(EC)DHE)
client_handshake_traffic_secret = Derive-Secret(Handshake Secret, "c hs traffic", ClientHello...ServerHello)
server_handshake_traffic_secret = Derive-Secret(Handshake Secret, "s hs traffic", ClientHello...ServerHello)
client_write_key = HKDF-Expand-Label(client_handshake_traffic_secret, "key", "", key_length)
client_write_iv = HKDF-Expand-Label(client_handshake_traffic_secret, "iv", "", iv_length)
server_write_key = HKDF-Expand-Label(server_handshake_traffic_secret, "key", "", key_length)
server_write_iv = HKDF-Expand-Label(server_handshake_traffic_secret, "iv", "", iv_length)
EncrytedExtension
不需要建立加密上下文并且和证书无关的拓展
CertificateRequest
请求客户端方的认证
Certificate
服务端发送证书认证
CertificateVerify
对之前的握手消息做签名,主动认证拥有私钥
Finished
计算finished的秘钥也是从之前的handshake_traffic_secret秘钥中导出
finished_key =
HKDF-Expand-Label(BaseKey, "finished", "", Hash.length)
BaseKey 是 handshake_traffic_secret。
然后计算verify_data
verify_data =
HMAC(finished_key,
Transcript-Hash(Handshake Context,
Certificate*, CertificateVerify*))
生成master secert以及对称key, iv
生成master secret然后再从master secret生成所需的对称key和iv,对之后的应用流量以及alert流量使用应用流量程序密钥加密
Master Secret = HKDF-Extract(salt, IKM) = HKDF-Extract(Derive-Secret(Handshake Secret, "derived", ""), 0)
client_application_traffic_secret_0 = Derive-Secret(Master Secret, "c ap traffic", ClientHello...server Finished)
server_application_traffic_secret_0 = Derive-Secret(Master Secret, "s ap traffic", ClientHello...server Finished)
Client Certificate,CertificateVerify,Finished
类似上面Server
NewSessionTicket
通过master secret和之前握手的摘要进行计算,对于不请求client身份验证的可以直接计算在server finished后发送NewSessionTicket消息
struct {
uint32 ticket_lifetime;
uint32 ticket_age_add;
opaque ticket_nonce<0..255>;
opaque ticket<1..2^16-1>;
Extension extensions<0..2^16-2>;
} NewSessionTicket;
extensions中的拓展early_data用来表示是否接受0-RTT模式,early_data中的max_early_data_size表示允许client能够发送的最大数据量
PSK相关计算为
HKDF-Expand-Label(resumption_master_secret,
"resumption", ticket_nonce, Hash.length)
KeyUpdate
因为AEAD模式下长时间同一个密钥加密大量的数据,会有安全问题,所以需要密钥更新机制,需要重新计算application_traffic_secret
application_traffic_secret_N+1 =
HKDF-Expand-Label(application_traffic_secret_N,
"traffic upd", "", Hash.length)
Record层
struct {
ContentType opaque_type = application_data; /* 23 */
ProtocolVersion legacy_record_version = 0x0303; /* TLS v1.2 */
uint16 length;
opaque encrypted_record[TLSCiphertext.length];
} TLSCiphertext;
加密后的结构如上
分块
填充(隐藏流量)
加密和完整性保护(AEAD模式)
输入:
write_key
nonce:从序列号和write_key生产出来
additional data:
additional_data = TLSCiphertext.opaque_type || TLSCiphertext.legacy_record_version || TLSCiphertext.length
明文
1.2和1.3的区别是nonce生成的不同,seq_num在1.2中是在additional_data中,1.3中是算入nonce中的
添加消息头
密钥计算
HKDF函数分为Extract和Expand
Extract过程增加密钥材料的随机性,expand进行拓展,在1.2中PRF函数只有expand的过程默认密钥材料的随机性是足够的
HKDF-Extract就是Extract过程
HKDF-Extract(salt, IKM) -> PRK
salt盐可选,没有时用0填充HashLen长度
IKM:Input Keying Material
PRK: 生成的伪随机key
PRK = HMAC-Hash(salt, IKM)
Derive-Secret就是Expand过程
HKDF-Expand(PRK, info, L) -> OKM
PRK伪随机key
info可选一般标志上下文信息
L期望输出的字节数
OKM生成方式:
N = ceil(L/HashLen)
T = T(1) | T(2) | T(3) | ... | T(N)
OKM = first L octets of T
where:
T(0) = empty string (zero length)
T(1) = HMAC-Hash(PRK, T(0) | info | 0x01)
T(2) = HMAC-Hash(PRK, T(1) | info | 0x02)
T(3) = HMAC-Hash(PRK, T(2) | info | 0x03)
...
以及:
HKDF-Expand-Label(Secret, Label, Context, Length) =
HKDF-Expand(Secret, HkdfLabel, Length)
Where HkdfLabel is specified as:
struct {
uint16 length = Length;
opaque label<7..255> = "tls13 " + Label;
opaque context<0..255> = Context;
} HkdfLabel;
Derive-Secret(Secret, Label, Messages) =
HKDF-Expand-Label(Secret, Label,
Transcript-Hash(Messages), Hash.length)
然后这个是整个1.3的密钥生成流程
0
|
v
PSK -> HKDF-Extract = Early Secret
|
+-----> Derive-Secret(., "ext binder" | "res binder", "")
| = binder_key
|
+-----> Derive-Secret(., "c e traffic", ClientHello)
| = client_early_traffic_secret
|
+-----> Derive-Secret(., "e exp master", ClientHello)
| = early_exporter_master_secret
v
Derive-Secret(., "derived", "")
|
v
(EC)DHE -> HKDF-Extract = Handshake Secret
|
+-----> Derive-Secret(., "c hs traffic",
| ClientHello...ServerHello)
| = client_handshake_traffic_secret
|
+-----> Derive-Secret(., "s hs traffic",
| ClientHello...ServerHello)
| = server_handshake_traffic_secret
v
Derive-Secret(., "derived", "")
|
v
0 -> HKDF-Extract = Master Secret
|
+-----> Derive-Secret(., "c ap traffic",
| ClientHello...server Finished)
| = client_application_traffic_secret_0
|
+-----> Derive-Secret(., "s ap traffic",
| ClientHello...server Finished)
| = server_application_traffic_secret_0
|
+-----> Derive-Secret(., "exp master",
| ClientHello...server Finished)
| = exporter_master_secret
|
+-----> Derive-Secret(., "res master",
ClientHello...client Finished)
= resumption_master_secret
- HKDF-Extract 画在图上,它为从顶部获取 Salt 参数,从左侧获取 IKM 参数,它的输出是底部,和右侧输出的名称。
- Derive-Secret 的 Secret 参数由传入的箭头指示。例如,Early Secret 是生成 client_early_traffic_secret 的 Secret。
- "0" 表示将 Hash.length 字节的字符串设置为零。
三个从Secret中Extract来:
Early Secret = HKDF-Extract(salt, IKM) = HKDF-Extract(0, PSK) = HKDF-Extract(0,0)
(有PSK的情况) (没有PSK的情况)
Handshake Secret = HKDF-Extract(salt, IKM) = HKDF-Extract(Derive-Secret(Early Secret, "derived", ""), (EC)DHE)
Master Secret = HKDF-Extract(salt, IKM) = HKDF-Extract(Derive-Secret(Handshake Secret, "derived", ""), 0)
八个可能会使用到的密钥:
client_early_traffic_secret = Derive-Secret(Early Secret, "c e traffic", ClientHello)
early_exporter_master_secret = Derive-Secret(Early Secret, "e exp master", ClientHello)
client_handshake_traffic_secret = Derive-Secret(Handshake Secret, "c hs traffic", ClientHello...ServerHello)
server_handshake_traffic_secret = Derive-Secret(Handshake Secret, "s hs traffic", ClientHello...ServerHello)
client_application_traffic_secret_0 = Derive-Secret(Master Secret, "c ap traffic", ClientHello...server Finished)
server_application_traffic_secret_0 = Derive-Secret(Master Secret, "s ap traffic", ClientHello...server Finished)
exporter_master_secret = Derive-Secret(Master Secret, "exp master", ClientHello...server Finished)
resumption_master_secret = Derive-Secret(Master Secret, "res master", ClientHello...client Finished)
两个Export secret导出密钥,用户自定义使用方式;resumption master secret用于会话恢复;其余五个secret想要加密还需要一次Expand过程生成write_key和iv
[sender]_write_key = HKDF-Expand-Label(Secret, "key", "", key_length)
[sender]_write_iv = HKDF-Expand-Label(Secret, "iv", "", iv_length)
resumption_master_secret为了计算出PSK,服务器计算出ticket的值,包含在NewSessionTicket中发送给客户端,客户端利用ticket,为PskIdentity
PskIdentity.identity = ticket
= HKDF-Expand-Label(resumption_master_secret, "resumption", ticket_nonce, Hash.length)
然后再计算出PskBinderEntry
PskBinderEntry = HMAC(binder_key, Transcript-Hash(Truncate(ClientHello1)))
= HMAC(Derive-Secret(HKDF-Extract(0, PSK), "ext binder" | "res binder", ""), Transcript-Hash(Truncate(ClientHello1)))
其中binder_key = Derive-Secret(HKDF-Extract(0, PSK), "ext binder" | "res binder", "")
然后client将以上两者合成为PSK,在会话恢复时作为ClientHello的拓展发送给Server。
PSK会作为Early Secret的IKM,然后从Early Secret生成client_early_traffic_secret,
Early Secret = HKDF-Extract(salt, IKM) = HKDF-Extract(0, PSK)
client_early_traffic_secret = Derive-Secret(Early Secret, "c e traffic", ClientHello)
再由client_early_traffic_secret生成key和iv用来加密0-RTT发送的early data
会话恢复
Server会发送
struct {
uint32 ticket_lifetime;
uint32 ticket_age_add;
opaque ticket_nonce<0..255>;
opaque ticket<1..2^16-1>;
Extension extensions<0..2^16-2>;
} NewSessionTicket;
ticket字段:
PskIdentity.identity = ticket
= HKDF-Expand-Label(resumption_master_secret, "resumption", ticket_nonce, Hash.length)
Client 收到 NewSessionTicket 以后就可以生成 PskIdentity 了,如果有多个 PskIdentity,就都放在 identities 数组中。binders 数组中是与 identities 顺序一一对应的 HMAC 值 PskBinderEntry。
struct {
opaque identity<1..2^16-1>;
uint32 obfuscated_ticket_age;
} PskIdentity;
opaque PskBinderEntry<32..255>;
struct {
PskIdentity identities<7..2^16-1>;
PskBinderEntry binders<33..2^16-1>;
} OfferedPsks;
struct {
select (Handshake.msg_type) {
case client_hello: OfferedPsks;
case server_hello: uint16 selected_identity;
};
} PreSharedKeyExtension;
binder的计算:
PskBinderEntry = HMAC(binder_key, Transcript-Hash(Truncate(ClientHello1)))
= HMAC(Derive-Secret(HKDF-Extract(0, PSK), "ext binder" | "res binder", ""), Transcript-Hash(Truncate(ClientHello1)))
其中的binder_key:
binder_key = Derive-Secret(HKDF-Extract(0, PSK), "ext binder" | "res binder", "")
最终得到PSK
0-RTT
Client Server
ClientHello
+ early_data
+ key_share*
+ psk_key_exchange_modes
+ pre_shared_key
(Application Data*) -------->
ServerHello
+ pre_shared_key
+ key_share*
{EncryptedExtensions}
+ early_data*
{Finished}
<-------- [Application Data*]
(EndOfEarlyData)
{Finished} -------->
[Application Data] <-------> [Application Data]
- Server有max_early_data_size拓展在NewSessionTicket中
- 会话恢复时,ClientHello有early_data
- Server在Encrypted Extensions中回复early data
client在发送完early data后会发送一个EndOfEarlyData
0-RTT安全问题:
- 从PSK中导出,没有前向安全
- 在生成earlydata加密时只Client Random,所以服务端不能防重放
具体的安全性分析放在后面。
总结
这里简单的过了下tls1.3的基本流程,下一篇会分析一些问题,以及1.2,1.3的安全性,性能分析
参考: