前言

2018年底正式发布了TLS1.3,对TLS1.2整体进行了升级,从安全性,性能方面大大提升,但是并没有做完全的前向兼容,并且目前的工业届也在逐渐支持使用TLS1.3,这里就简单的分析学习下。

正文

主要改变

  1. 秘钥拓展函数从PRF升级为HKDF
  2. 删除了不安全的密码组件如RC4,MD5等
  3. 秘钥协商只用ECDHE保证了前向安全
  4. 提前带上支持的密码组件和参数从而冷启动时2-RTT变为1-RTT
  5. PSK模式代替session_id和session_ticket
  6. 基于PSK和early_data的0-RTT模式
  7. 删除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模式)

    输入:

    1. write_key

    2. nonce:从序列号和write_key生产出来

    3. additional data:

         additional_data = TLSCiphertext.opaque_type ||
                           TLSCiphertext.legacy_record_version ||
                           TLSCiphertext.length
    4. 明文

    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
  1. HKDF-Extract 画在图上,它为从顶部获取 Salt 参数,从左侧获取 IKM 参数,它的输出是底部,和右侧输出的名称。
  2. Derive-Secret 的 Secret 参数由传入的箭头指示。例如,Early Secret 是生成 client_early_traffic_secret 的 Secret。
  3. "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]
  1. Server有max_early_data_size拓展在NewSessionTicket中
  2. 会话恢复时,ClientHello有early_data
  3. Server在Encrypted Extensions中回复early data

client在发送完early data后会发送一个EndOfEarlyData

0-RTT安全问题:

  1. 从PSK中导出,没有前向安全
  2. 在生成earlydata加密时只Client Random,所以服务端不能防重放

具体的安全性分析放在后面。

总结

这里简单的过了下tls1.3的基本流程,下一篇会分析一些问题,以及1.2,1.3的安全性,性能分析

参考:

冰霜之地

TLS1.3 RFC