密钥交换的概念

密钥交换,也有称作密钥协商,这套机制,最主要的作用是用来得到通信双方的临时会话密钥。

这里的临时会话密钥,可以理解为对称加密的密钥,只不过他的有效性仅限于一次会话链接,并不是长期有效的。

前面其实我们分析过,分对称密钥比如RSA也是可以做加解密的,那么在实际的通信过程比如TLS中,为什么还需要生成临时的对称密钥呢?

这里最核心的原因有两个:

img

  1. 非对称密钥加解密,对于数据量有比较严格的要求,比如RSA算法,在使用OAEP填充模式时,每次最多只能加密190字节。
  2. 非对称密钥加解密的性能相对于对称密钥,差了很多,在这实际的业务流加解密中,无法进行业务落地。因此在实际的工程化上,一般使用非对称密钥进行数据密钥的协商与交换,而使用数据密钥与对称加密算法进行数据流的加解密保护。

基于RSA的密钥交换

简单的密钥交换过程

基于RSA进行密钥交换,基于非对称密钥的两个基本特性:

  • 使用公钥加密、私钥解密,且此过程无法逆向
  • 公钥是对外公开的,私钥是私密不公开的

客户端与服务端

在简单的密钥交换场景中,有两个基本角色,客户端服务端

客户端与服务端进行正常的业务流通信前,总是需要先线上一把数据密钥,用于给业务流进行加解密。

这里我们约定服务端总是权威的,其公钥是被所有客户端所感知的,客户端是任意的,没有身份上的要求,客户端总是主动发起于服务端的通信,并且服务端总是回复可信的数据给客户端。

服务端分发公钥给客户端

服务端分发公钥给客户端

客户端是密钥生成的决定方

在基于RSA的密钥交换体系中,总是由客户端来生成密钥。

这是因为,客户端总是能够获取到服务端的公钥,由客户端生成随机密钥,然后由服务端使用私钥解密,这样可以保证随机密钥的保密性。

随机密钥的加密与传输

随机密钥的加密与传输

服务端使用私钥获取随机密钥明文

私钥总是由服务端私密保存,绝对不会公开。

这是基于RSA进行数据安全加密的前提。

也是基于RSA进行密钥交换的基础。

随机密钥的解密与使用

随机密钥的解密与使用

服务端在使用私钥对随机密钥密文解密后便默认承认了与客户端使用的是同一把随机密钥。

随后的业务流数据便使用这把随机密钥进行数据通信。

针对RSA密钥交换的中间人攻击

客户端无法区分公钥来源

客户端是无法区分公钥来源的,这是RSA可能被中间人攻击的前提。

客户端只能够使用公钥对随机密钥进行加密,但是,这份公钥究竟是属于真实服务端,还是属于中间人的,客户端自己无法区分。

中间人迷惑客户端

中间人迷惑客户端

中间人对客户端的拦截

一旦客户端接受了中间人的公钥,那么就意味着客户端默认了中间人是真实的服务端。

那么在前面我们描述的数据密钥的交换过程,就可以被中间人完全监听甚至是篡改。

拦截客户端消息

拦截客户端消息

客户端在生成随机密钥并加密后,其消息会被中间人全部监听,由于客户端使用的是中间人的公钥,因此即使随机密钥被加密,中间人还是可以完全获取其明文。

中间人对服务端的迷惑

中间人同样可以对服务端进行迷惑。

比如,在使用自己的私钥对随机密钥解密后,再试用服务端公钥对随机密钥加密,然后发送给服务端。

此时的服务端,其实也无法区分,发送数据过来的,究竟是客户端还是中间人。

迷惑服务端

中间人对消息进行监听

在RSA密钥交换过程中,中间人需要保证:

  • 截取客户端的真实数据密钥
  • 迷惑服务端,使其相信中间人投递的数据密钥就是客户端产生的真实数据密钥

完成上述两个步骤后,中间人就可以进行双向的消息监听甚至篡改。

中间人消息监听

避免RSA的中间人攻击

防止中间人攻击的方法实际上就是身份证认证方式,目前主流方式就是数字签名的方式。

在RSA的这种密钥交换过程中,同样可以很好地应用证书来进行身份的鉴别与认证。

对服务端的身份认证

对服务端的身份认证

在对服务端的认证过程中,最重要的仍然是要解决三个问题:

身份认证的关键

  • 证书是否合法
  • 证书是否被正确持有
  • 证书持有者是否合法

这里细节就不赘述了,在前文中有详细描述,感兴趣的可以翻一下《非对称密钥沉思系列(3):公钥、签名与证书》。

关于RSA交换的更多思考

在前面的描述中,我们其实只是着重的描述了客户端使用公钥加密随机密钥,然后服务端使用私钥对随机密钥解密的过程。其实结合《非对称密钥沉思系列(2):聊聊RSA与数字签名》中的内容,我们在做随机密钥的交换时,还可以结合对随机密钥的HMAC等手段,保证随机密钥的不被篡改。这里感兴趣的同学可以自己思考下。

密钥交换协议DH

前面我们聊了很多RSA,但其实,RSA更侧重于非对称密钥算法,主要功能其实还是在于加密与解密。

而密钥交换协议DH,是专门用于协商密钥生成的。

RSA可以用来传输信息,DH更适合用来协商密钥。

DH算法解决了密钥在双方不直接传递密钥的情况下完成密钥交换,这个神奇的交换原理完全由数学理论支持。

由于DH系列算法涉及比较复杂的数学推理运算,这里不做过多展开讲解,感兴趣的同学可以翻阅相关RFC文档等。

DH协议的安全性

最原始的DH算法并不能对抗MIMT(Man-in-the-middle attack),所以一般需要配合签名技术:

  • 不配合签名技术的DH称为:DH-ANON
  • 配合RSA签名的称为:DH-RSA
  • 配合DSA签名的称为:DH-DSA
  • 配合ECDSA签名的称为:DH-ECDSA

总的来说,DH协议也怕中间人攻击,一般来说,也需要配置数字证书来进行身份的认证。

关于Forward security前向保密

Forward security前向保密,最初用来定义绘画密钥交换协议的一种安全性。即使长期密钥已经泄露,也不会影响之前的会话密钥的泄露,也就不会暴露之前的会话内容。

DH和ECDH算法为了实现前向安全,变种加入了另一个随机变量ephemeral key得到新的算法DHE、ECDHE。

算法对比

  • RSA、DH和DSA都是基于整数有限域离散对数来实现
  • ECC和ECDH都是基于椭圆曲线的离散对数难题来实现的

现在实际使用中,优先选择 ECDHE > DHE > DH > RSA 等。

python库中关于DH协议使用的示例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
from typing import Tuple
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import dh
from cryptography.hazmat.primitives.asymmetric.dh import DHParameters, DHPrivateKey, DHPublicKey
from cryptography.hazmat.primitives.kdf.hkdf import HKDF

def data_key_exchange(owner_pri_key: DHPrivateKey, peer_pub_key: DHPublicKey) -> bytes:
    shared_key = owner_pri_key.exchange(peer_pub_key)
    derived_key = HKDF(
        algorithm=hashes.SHA256(),
        length=32,
        salt=None,
        info=b'handshake data',
        backend=default_backend()
    ).derive(shared_key)
    print("derived_key:{}, length:{}".format(list(derived_key), len(derived_key)))
    return derived_key

def generate_common_parameters() -> DHParameters:
    parameters = dh.generate_parameters(
        generator=2,
        key_size=2048,
        backend=default_backend()
    )
    return parameters

def generate_dh_key(parameters: DHParameters) -> Tuple[DHPrivateKey, DHPublicKey]:
    private_key = parameters.generate_private_key()
    public_key = private_key.public_key()
    return private_key, public_key

if __name__ == '__main__':
    param = generate_common_parameters()
    a_pri_key, a_pub_key = generate_dh_key(param)
    b_pri_key, b_pub_key = generate_dh_key(param)
    
    a_data_key = data_key_exchange(a_pri_key, b_pub_key)
    b_data_key = data_key_exchange(b_pri_key, a_pub_key)
    
    print(a_data_key == b_data_key)

最终生成的密钥数据:

1
2
3
derived_key:[3, 139, 194, 229, 249, 124, 131, 14, 141, 172, 168, 29, 26, 152, 63, 253, 227, 234, 189, 101, 130, 192, 139, 99, 50, 8, 153, 75, 31, 247, 200, 24], length:32
derived_key:[3, 139, 194, 229, 249, 124, 131, 14, 141, 172, 168, 29, 26, 152, 63, 253, 227, 234, 189, 101, 130, 192, 139, 99, 50, 8, 153, 75, 31, 247, 200, 24], length:32
True