赞
踩
通过对TLS1.2的学习,我了解到其中具有三种密钥:预主密钥、主密钥和会话密钥。
在其握手过程中,客户端和服务器都会根据密码套件生成自己的临时公私钥对,并且将公钥发送给对方,当客户端接收到服务器的公钥时,会生成预主密钥。
之后客户端会使用PRF函数计算出主密钥发送给服务器,计算时需要三个输入:
如果使用的是RSA,那么预主密钥的结构如下:
struct {
uint32 gmt_unix_time;
opaque random_bytes[28];
} Random;
struct {
ProtocolVersion client_version;
opaque random[46];
} PreMasterSecret;
struct {
uint8 major;
uint8 minor;
} ProtocolVersion;
会话密钥也是通过PRF函数生成的,需要输入:
Session ID 缓存和 Session Ticket 里面保存的也是主密钥,而不是会话密钥,这样每次会话复用的时候再用双方的随机数和主密钥导出会话密钥,从而实现每次加密通信的会话密钥不一样,即使一个会话的主密钥泄露了或者被破解了也不会影响到另一个会话。
它使用一个 hash 函数扩展成一个 secret 和种子,形成任意大小的输出。其中的哈希函数取决于协商的密码套件。
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))
"+"表示连接,HMAC_hash的运算次数取决于想要结果的长度。
在计算不同类型的密钥时,对应的secret值也是不同的
PRF(secret, label, seed) = P_<hash>(secret, label + seed)
label必须是ASCII字符串,不包含结尾的’\0’。
通过前面的介绍,我们已经大概了解了TLS1.2中的密钥生成实现过程,在TLS1.3中主要使用的是HKDF中的 HKDF-Extract 和 HKDF-Expand 函数,HKDF的主要的目标是获取一些初始的密钥材料,并从中派生出一个或多个安全强度很大的密钥。主要分为两步:
HKDF-Extract(salt, IKM) -> PRK
使用协商好的哈希算法将输入的密钥尽量的伪随机化。
通过一系列的哈希运算,将密钥扩展到我们所需要的长度,有点类似TLS1.2中的P_HASH。
HKDF-Expand(PRK,info,L)-> OKM
下面看一下OKM的计算方式:
N = ceil(L / HashLen)
T = T(1)| T(2)| T(3)| ... | T(N)
OKM = T的前L个八位位组,
其中:
T(0)= 空字符串(零长度)
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)
...
(其中,连接到每个T(n)末尾的常数是一个
八位位组。)
n为0-255的整数,L除以所用哈希函数输出摘要的长度,再向上取整,我们将其记为n。
然后将T(n)串接起来,而我们想要获得的OKM正是取T的前L个字节组成。这要我们就得到了我们想要的密钥材料。
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)
Transcript-Hash 和 HKDF 使用的 Hash 函数是密码套件哈希算法,Hash.length 是其输出长度(以字节为单位)。消息是表示的握手消息的串联,包括握手消息类型和长度字段,但不包括记录层头,labels 都是 ASCII 字符串。
我们可以看到由于HKDF-Expand-Label(Secret, Label, Context, Length) = HKDF-Expand(Secret, HkdfLabel, Length)
所以Derive-Secret
相当于HKDF中的HKDF-Expand
函数,所以TLS1.3中整个的密钥生成过程主要还是由HKDF中的两个函数计算得来。
其中的HkdfLabel:
HkdfLabel = Hash.length(2 字节) + label_length(1字节) + "tls13 " + Label + Hash.length(1字节) + HASH(Messages)
TLS 中的许多加密计算都使用了哈希副本。这个值是通过级联每个包含的握手消息的方式进来哈希计算的,它包含握手消息头部携带的握手消息类型和长度字段,但是不包括记录层的头部。我的理解是不包括ContenType、legacyRecordVersion、length
字段的数据。示例:
Transcript-Hash(M1, M2, ... Mn) = Hash(M1 || M2 || ... || Mn)
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
从上面开始分析,PSK和0通过HKDF-Extract计算得到Early Secret,然后右侧的第一个是使用Derive-Secret函数生成binder_key
,这是什么呢?我们再分析一下之前理解的不是很清楚的PreSharedKey
:
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;
那么我们主要来说一下PSK binders
,也就是PskBinderEntry
是怎样计算的:
它的计算过程与 Finished 消息一致,只不过其中的Basekey是通过PSK派生的,也就是binder_key
。
The PskBinderEntry is computed in the same way as the Finished message (Section 4.4.4) but with the BaseKey being the binder_key.
binder_key
,前面已经说过计算过程了Finished
计算过程:HMAC(Transcript-Hash(all_handshake), finished_key)
所以我们还需要计算finished_key
finished_key
,先看公式:finished_key = HKDF-Expand-Label(BaseKey, "finished", "", Hash.length)
client_handshake_traffic_secret
binder_key
,所以已经可以计算得出,我们只需要计算的是:Transcript-Hash(all_handshake)
Transcript-Hash
,其中不包括PSK Binders
和PSK Binders length
,然后和finished_key
一起计算HMAC
值,即可得出最终结果。那我们接着往下看,如果给定的 secret 不可用,则使用由设置为零的 Hash.length 字节串组成的 0 值。它不会跳过本轮次,所示当不适用PSK时,Early Secret 仍将是 HKDF-Extract(0,0)。
至于client_early_traffic_secret
和early_exporter_master_secret
的作用是需要使用会话恢复时,生成的相关密钥。
然后再往下看,Early Secret
经过Derive-Secret
计算的结果作为下面计算Handshake Secret
的salt
,(EC)DHE
作为IKM
。(EC)DHE
是server和client协商出的公钥。然后又扩展成xxx_handshake_traffic_secret
,其中都是把clienthello和serverhello
中的信息作为Messages
来计算的。
继续往下的话,依然是这样的原理,后面的Messages
组成部分有Finished
。说一下密钥导出吧,我也还不是很理解这是啥意思,慢慢理解吧,先写了:
TLS-Exporter(label, context_value, key_length) =
HKDF-Expand-Label(Derive-Secret(Secret, label, ""),
"exporter", Hash(context_value), key_length)
其中的Secret
是右侧计算出的exporter_master_secret
或early_exporter_master_secret
最后再来说一下,右侧生成的xxx_traffic_secret
都叫做Basekey
,应用于不同的场景,并且这些密钥也不会用来加密数据,最终还需要:
[sender]_write_key = HKDF-Expand-Label(Secret, "key", "", key_length)
[sender]_write_iv = HKDF-Expand-Label(Secret, "iv" , "", iv_length)
我们可以把它们理解为中间变量,再需要一次HKDF-Expand
计算就可以得到对应的加密密钥了。
[sender] 表示发送方。每种记录类型的 Secret 值显示在下表中:
+-------------------+---------------------------------------+
| Record Type | Secret |
+-------------------+---------------------------------------+
| 0-RTT Application | client_early_traffic_secret |
| | |
| Handshake | [sender]_handshake_traffic_secret |
| | |
| Application Data | [sender]_application_traffic_secret_N |
+-------------------+---------------------------------------+
由 client_early_traffic_secret
生成的 write_key
和 write_iv
最终用于 0-RTT 的加密和解密。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。