我的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调用了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算法很简单。
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,两次的结果进行亦或才是最后的结果。
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本身不复杂,但是 在 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 的图:
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右侧的 Derive-Secret
, 那么流程图就变成了如下图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。
psk
和0
生成 early secret
early secret
和pms
生成handshake secret
handshake secret
和0
生成master secret
计算出来的 early secret
、handshake secret
、master 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
中实现 。
HKDF导出的流程中,还会导出一个finished_key
。
finished 计算
HMAC( MD(all_handshake), finished_key)
而
finished_key = HKDF-Expand-Label(BaseKey, "finished", "", Hash.length)
BaseKey
就是 handshake_traffic_secret