一. TLS 1.2 中的密钥
在 TLS 1.2 中,有 3 种密钥:预主密钥、主密钥和会话密钥(密钥块),这几个密钥都是有联系的。
1. 预主密钥 PreMasterSecret
-
对于 RSA 握手协商算法来说,Client 会生成的一个 48 字节的预主密钥,其中前 2 个字节是 ProtocolVersion,后 46 字节是随机数,用 Server 的公钥(服务器公钥证书certificate)加密之后通过 Client Key Exchange 子消息发给 Server,Server 用私钥来解密。
struct { uint32 gmt_unix_time; opaque random_bytes[28]; } Random; struct { ProtocolVersion client_version; opaque random[46]; } PreMasterSecret; struct { uint8 major; uint8 minor; } ProtocolVersion;
-
对于 (EC)DHE握手协商算法来说,预主密钥是双方通过椭圆曲线算法生成的,双方各自生成临时公私钥对,保留私钥,将公钥发给对方,然后就可以用自己的私钥以及对方的公钥通过椭圆曲线算法来生成预主密钥,预主密钥长度取决于 DH/ECDH 算法公钥。预主密钥长度是 48 字节或者 X 字节。
2. 主密钥
主密钥是由「预主密钥、ClientHello random 和 ServerHello random 」通过 PRF 函数生成的。主密钥长度是 48 字节。
只要我们知道预主密钥或者主密钥便可以解密抓包数据,所以 TLS 1.2 中抓包解密调试只需要一个主密钥即可,SSLKEYLOG 就是将主密钥导出来,在 Wireshark 里面导入就可以解密相应的抓包数据。
3. 会话密钥(密钥块)
会话密钥(密钥块)是由「主密钥、SecurityParameters.server_random 和 SecurityParameters.client_random 」数通过 PRF 函数来生成。
会话密钥里面包含对称加密密钥、消息认证和 CBC 模式的初始化向量,对于非 CBC 模式的加密算法来说,就没有用到这个初始化向量。
4. 会话复用
Session ID 缓存和 Session Ticket 里面保存的也是主密钥,而不是会话密钥,这样每次会话复用的时候再用双方的随机数和主密钥导出会话密钥,从而实现每次加密通信的会话密钥不一样,即使一个会话的主密钥泄露了或者被破解了也不会影响到另一个会话。
二. TLS 1.2 中的 HMAC 和伪随机函数
1. HMAC
HMAC:基于哈希函数和密钥的消息认证码。在TLS握手过程中,HMAC用于计算和验证握手消息的MAC值,验证消息的完整性和认证。
2. 伪随机函数PRF
为了进行密钥生成或验证,需要一个 MAC 算法对数据块进行扩展以增加机密性。这个伪随机函数(PRF)将 secret,seed 和 label 作为输入,产生任意长度的输出。
在 TLS 1.2 中,基于 HMAC 定义了一个 PRF 函数。这个使用 SHA-256 hash 函数的 PRF 函数被用于所有的密码算法套件。
首先,我们定义一个数据扩展函数 P_hash(secret, data),它是PRF中的一个子函数,用于生成伪随机数据。它使用一个 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(0) = seed
A(i) = HMAC_hash(secret, A(i-1))
必要时 P_hash 可以被多次迭代,以产生所需数量的数据。例如,如果 P_SHA256 被用于产生 80 字节的数据,它应该被迭代 3 次(通过 A(3)),SHA_256 每次输出 32 字节(256 bit),迭代 3 次才能产生 96 字节的输出数据,最终迭代产生的最后 16 字节会被丢弃,留下 80 字节作为输出数据。
TLS 的 PRF 可以通过将 P_hash 运用与 secret 来实现:
PRF(secret, label, seed) = P_<hash>(secret, label + seed)
// 将label + seed 视为 seed
其中,label 是一个 ASCII 字符串。它应该以严格地按照它被给出的内容转化为十六进制格式。
三. TLS 1.2 中的密钥计算
1. 计算主密钥
为了开始连接保护,TLS 记录协议要求指定一个算法套件,一个主密钥和 Client 及 Server 端随机数。认证,加密和消息认证码算法由 cipher_suite 确定,cipher_suite 是由 Server 选定并在 ServerHello 消息中表明出来的。压缩算法在 hello 消息里协商出来,随机数也在 hello 消息中交换。所有这些都用于计算主密钥。
对于所有的密钥交换算法,相同的算法都会被用来将 pre_master_secret 转化为 master_secret。一旦 master_secret 计算完毕,pre_master_secret就应当从内存中删除。避免攻击者获取预备主密钥,如果攻击者获取到了预主密钥,加上 ClientHello.random 和 ServerHello.random 传输过程中是不加密的,也容易获取,那么攻击者就可以合成主密钥并进一步导出会话密钥,这样整个加密过程就被完全破解了。
master_secret = PRF(pre_master_secret, "master secret",
ClientHello.random + ServerHello.random)
[0..47];
主密钥的长度一直是 48 字节。预主密钥的长度根据密钥交换算法而变。
-
RSA
当RSA被用于身份认证和密钥交换时,Client 会产生一个 48 字节的 pre_master_secret,用 Server 的公钥加密,然后发送给 Server。Server 用它自己的私钥解密 pre_master_secret。然后双方按照前述方法将 pre_master_secret转换为 master_secret。
-
Diffie-Hellman
一个传统的 Diffie-Hellman 计算需要被执行。协商出来的密钥(Z)会被用做pre_master_secret,并按照前述方法将其转换为 master_secret。在被用做pre_master_secret之前,Z 开头所有的 0 位都会被压缩。
注:Diffie-Hellman 参数由 Server 指定,可能是临时的也可能包含在 Server 的证书中。
2. 计算增强型主密钥(开启EMS)
如果 ClientHello 的扩展中携带了 extended_master_secret 扩展,表示 Client 和 Server 使用增强型主密钥计算方式。
Server 在 ServerHello 中响应该扩展,返回了一个空的 extended_master_secret 扩展,表明会使用增强型主密钥计算方式。
那么增强型主密钥是如何计算的呢?计算方式如下:
master_secret = PRF(pre_master_secret, "extended master secret",
session_hash)
[0..47];
上面的计算方式和普通计算主密钥方式不同点在于:
- “extended master secret” 替代了 “master secret”
- session_hash 替代了 ClientHello.random + ServerHello.random
“session_hash” 的计算取决于多个因素,包括密码套件、密钥交换信息、证书(如果有的话)、“ClientHello.random” 和 “ServerHello.random” 。
3. 计算会话密钥
主密钥被扩张为一个安全字节序列,它被分割为四项:为client_write_MAC_key
,server_write_MAC_key
,client_write_key
, server_write_key
。它们都是从字节序列中以上述顺序生成。未使用的值是空。
一些AEAD加密可能会额外需要一个 client_write_IV
和一个 server_write_IV
。
因此,会话密钥(密钥块)的长度和个数取决于协商出来的密码套件,更准确的说是取决于加密参数 SecurityParameters,需要使用 PRF 函数扩展出足够长的密钥块,计算如下:
key_block = PRF(SecurityParameters.master_secret,
"key expansion",
SecurityParameters.server_random +
SecurityParameters.client_random);
注意:计算会话密钥和主密钥使用 PRF 的三个入参都不同,PRF(secret, label, seed)
:
-
主密钥是
(pre_master_secret, "master secret", ClientHello.random + ServerHello.random)
-
会话密钥是
(SecurityParameters.master_secret, "key expansion", SecurityParameters.server_random + SecurityParameters.client_random)
seed 顺序有变化,Client 和 Server 随机数的组合顺序会调换。
直到产生足够的输出。然后,key_block会按照如下方式分开:
client_write_MAC_key[SecurityParameters.mac_key_length]
server_write_MAC_key[SecurityParameters.mac_key_length]
client_write_key[SecurityParameters.enc_key_length]
server_write_key[SecurityParameters.enc_key_length]
client_write_IV[SecurityParameters.fixed_iv_length]
server_write_IV[SecurityParameters.fixed_iv_length]
client_write_key
、server_write_key
、client_write_MAC_key
和 server_write_MAC_key
是加密和消息验证码需要的密钥。Client 和 Server 分别拥有自己的一套密钥,使用的密钥是不同的。
如果是分组加密方式,还需要初始化向量 client_write_IV
和 server_write_IV
。如果是 AEAD 模式,client_write_MAC_key
和 server_write_MAC_key
可以不需要,使用 client_write_IV
和 server_write_IV
作为随机值。
一些用到的结构体如下:
enum { null(0), (255) } CompressionMethod;
enum { server, client } ConnectionEnd;
enum { tls_prf_sha256 } PRFAlgorithm;
enum { null, rc4, 3des, aes } BulkCipherAlgorithm;
enum { stream, block, aead } CipherType;
enum { null, hmac_md5, hmac_sha1, hmac_sha256, hmac_sha384,
hmac_sha512} MACAlgorithm;
/* Other values may be added to the algorithms specified in
CompressionMethod, PRFAlgorithm, BulkCipherAlgorithm, and
MACAlgorithm. */
struct {
ConnectionEnd entity; // 连接端点,可以是server或client
PRFAlgorithm prf_algorithm; // 目前仅支持tls_prf_sha256
BulkCipherAlgorithm bulk_cipher_algorithm; // 批量加密算法
CipherType cipher_type; // 加密算法类型
uint8 enc_key_length; // 加密密钥的长度
uint8 block_length; // 加密算法的块长度
uint8 fixed_iv_length; // 固定初始化向量(IV)的长度
uint8 record_iv_length; // 记录级别的初始化向量(IV)的长度
MACAlgorithm mac_algorithm; // MAC算法
uint8 mac_length; // MAC值的长度
uint8 mac_key_length; // MAC密钥的长度
CompressionMethod compression_algorithm; // 压缩算法
opaque master_secret[48];
opaque client_random[32];
opaque server_random[32];
} SecurityParameters;
4. 总结
四. TLS 1.2 Finished 校验
在 TLS 1.2 握手的最后,会发送 Finished 子消息,这条消息是加密的第一条消息,Finished 消息的接收者必须要验证这条消息的内容是否正确。验证的内容是通过 PRF 算法计算出来的。
verify_data = PRF(master_secret,
finished_label,
Hash(handshake_messages))
[0..verify_data_length-1];
1. finished_label
- Client:
finished_label = "client finished"
; - Server:
finished_label = "server finished"
,seed 是所有握手消息的 hash 值。
2. handshake_messages
- Client:包含所有发送的消息和接收的消息,但是不包括自己发送的 Finished 消息。
- Server:包含从 ClientHello 消息开始截止到 Finished 消息之前的所有消息,也包括 Client 的 Finished 子消息。
注意:handshake_messages 中只包含握手子消息,不包括 ChangeCipherSpec 子消息、 Alert 子消息、HelloRequest 消息。
3. verify_data 长度
早期 TLS 协议,verify_data 的长度是 12 字节,对于 TLS 1.2 协议来说,verify_data 的长度取决于密钥套件,如果密码套件没有指定 verify_data_length,则默认长度也是 12 字节。
五. TLS 1.3 中的密钥
在 TLS 1.3 中,不再使用 PRF 这种算法了,而是采用更标准的 HKDF 算法来进行密钥推导。而且在 TLS 1.3 中对密钥进行了更细粒度的优化,每个阶段或者方向的加密都不是使用同一个密钥。
TLS 1.3 在 ServerHello 消息之后的数据都是加密的,握手期间 Server 给 Client 发送的消息用 server_handshake_traffic_secret 通过 HKDF 算法导出的密钥加密的,Client 发送给 Server 的握手消息是用 client_handshake_traffic_secret 通过 HKDF 算法导出的密钥加密的。
这两个密钥是通过 Handshake Secret 密钥来导出的,而 Handshake Secret 密钥又是由 PreMasterSecret 和 Early Secret 密钥导出,然后通过 Handshake Secret 密钥导出主密钥 Master Secret。