TLS 协议中 PRF 和 TLS 1.3 中的 HKDF

我的TLS实现,大家可以参考:https://github.com/mrpre/atls/
TLS 1.3协议详解:https://blog.csdn.net/mrpre/article/details/81532469
TLS 1.0/1.1/1.2协议中,使用了PRF算法进行 key derive。
TLS 1.3中使用了标准的HKDF来进行key derive。

这里大概先回顾一下 <= TLS 1.2 时密钥导出流程和PRF算法使用场景。
然后再说一下 TLS 1.3 中的HKDF 算法 流程。

PRF算法:

PRF算法可以生成指定长度的数据

PRF调用了P_HASH算法
所以 先说P_HASH

(1) P_HASH

RFC定义:

P_hash(secret, seed) = HMAC_hash(secret, A(1) + seed) +
                       HMAC_hash(secret, A(2) + seed) +
                       HMAC_hash(secret, A(3) + seed) + ...
其中
A() 如下定义:
      A(0) = seed
      A(i) = HMAC_hash(secret, A(i-1))

所谓的P_HASH,其实具体点就是 P_SHA256、P_SHA1…等,所以P_HASH中HMAC具体使用的HASH算法依据的就是P_HASH的具体HASH算法。P_hash中计算HMAC_hash的次数,取决于想要的输出的长度。
P_HASH迭代调用HMAC,其实 实现并不复杂,一个大循环判断是当前结果否已经满足输出长度,在循环里,先算法A(x),然后再算HMAC_hash(secret, A(x) + seed)
所以PRF算法很简单。

(2)PRF

P_HASH说完,该说一下PRF了,上面说过PRF实际调用了 P_HASH,所以现在描述下PRF

TLS 1.0/1.1 中

r1 = P_MD5(...);
r2 = P_SHA1(...);
r  = r1 xor r2

TLS 1.2中

P_SHA256(...)或者P_SHA384(...)(具体使用哪个依据握手协商的加密套件)

可见,TLS1.2中,PRF算法其实就是直接调用了P_HASH算法,而TLS 1.0/1.1调用了两次P_HASH,一次是MD5一次是SHA1,两次的结果进行亦或才是最后的结果。

PRF算法在TLS协议中的运用

TLS 协议中:
(1)
当计算主密钥(master secret)时
sec 是 “master secret” + client_random + server_random
seed 是 pre_master_secret
输出是 master secret,48字节

(2)
当计算对称密钥(key material)时
sec 是 “key expansion” + server_random + client_random
seed 是 master secret
输出是key block,输出字节长度根据加密算法而定。
key block中的各个部分,就是实际加解密使用的key。

(3)
当计算finished时
sec 是 “client/server finished”
seed 是握手的hash值
输出是12字节的值。

HKDF

HKDF本身不复杂,但是 在 TLS1.3 中的应用就比较复杂了。
HKDF详细的Paper见该文:https://eprint.iacr.org/2010/264.pdf 但是我觉得没必要看,因为看不懂。

建议看下面2篇文章:
我的HKDF RFC翻译 https://blog.csdn.net/mrpre/article/details/79879392
我的HKDF 示例代码 https://blog.csdn.net/mrpre/article/details/79881184

HKDF 在 TLS1.3中的应用,先上 RFC 的 key derive 的图:

图1

                 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

说实话,这张图让我第一次对TLS协议产生了反感。

用到的函数

看似很复杂,但是捋一下其实不复杂。

上面出现2个函数
1:HKDF-Extract,这个函数就是HKDF的extract,它的实现见我上面给的链接。
2:Derive-Secret,这个要着重说一下

根据RFC的定义

Derive-Secret(Secret, Label, Messages)  
= HKDF-Expand-Label(Secret, Label,
                    Transcript-Hash(Messages), Hash.length)

HKDF-Expand-Label(Secret, Label, Context, Length) 
= HKDF-Expand(Secret, HkdfLabel, Length)

所以

Derive-Secret(Secret, Label, Messages) 
= HKDF-Expand(Secret, HkdfLabel, Hash.length)
struct {
    uint16 length = Length;
    opaque label<7..255> = "tls13 " + Label;
    opaque context<0..255> = Context;
} HkdfLabel;
其中
HkdfLabel = Hash.length(2 字节) + label_length(1字节) + "tls13 " + Label + Hash.length(1字节) + HASH(Messages)

也就是说Derive-Secret就是HKDF的expand操作。
Derive-Secret在OpenSSL中的实现是 tls13_hkdf_expand 函数。

至此我们可以总结出,上面这个流程图中,无非2个操作,HKDF的extract和HKDF的expand操作。

图解1 - 从上往下看

不考虑图1右侧的 Derive-Secret, 那么流程图就变成了如下图2形式:

图2

                 0
                 |
                 v
   PSK ->  HKDF-Extract = Early Secret
                 |
                 |
                 |
                 |
                 v
           Derive-Secret(., "derived", "")
                 |
                 v
(EC)DHE -> HKDF-Extract = Handshake Secret
                 |
                 |
                 |
                 |
                 |
                 v
           Derive-Secret(., "derived", "")
                 |
                 v
      0 -> HKDF-Extract = Master Secret
                 |
                 |
                 |
                 |
                 |
                 |

这个图正是 tls13_generate_secret 函数所干的事情,而这个函数被如下函数调用,
tls_psk_do_binder/ssl_derive 计算 early secret
tls13_generate_handshake_secret 计算 handshake secret
tls13_generate_master_secret 计算 master secret
正好印证了tls13_generate_secret的作用。

注意:tls13_generate_secret 实际函数执行的是如图三这个流程
图三

           Derive-Secret(., "derived", "")
                 |
                 v
      in secret -> HKDF-Extract = xxx
                 |
                 |
                 |
       

多次反复调用,就形成了图2。
psk0生成 early secret
early secretpms生成handshake secret
handshake secret0生成master secret

图解2 - 从左往右看

计算出来的 early secrethandshake secretmaster secret并不会直接用来加密数据,完全可以理解为是为了计算秘钥key而产生的临时变量。这些临时变量需要按照 图1 中右侧的流程计算,生成的XXX_traffic_secret ( XXX_traffic_secret被称之为basekey ),
然而XXX_traffic_secret也不会用来加密。。。还需要按照RFC 7.3 节中描述的步骤:

[sender]_write_key = HKDF-Expand-Label(Secret, "key", "", key_length)
[sender]_write_iv  = HKDF-Expand-Label(Secret, "iv" , "", iv_length)

其中 Secret 就是 XXX_traffic_secret 。而输出[sender]_write_key[sender]_write_iv就是加密时实际使用的参数了 。
这个流程在 tls13_derive_key 以及 tls13_derive_iv中实现 。

Finish key

HKDF导出的流程中,还会导出一个finished_key
finished 计算
HMAC( MD(all_handshake), finished_key)


finished_key = HKDF-Expand-Label(BaseKey, "finished", "", Hash.length)
BaseKey就是 handshake_traffic_secret

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐