DH (Diffie-Hellman)
RSA 既可以用于密钥交换,又可以用于数字签名;ECC 这边就分得比较清楚了:ECDHE 用于密钥交换,ECDSA 用于数字签名。
ECDHE 密钥交换使用的是 DH(Diffie-Hellman)算法。
Deffie-Hellman(简称 DH) 密钥交换是最早的密钥交换算法之一,它使得通信的双方能在非安全的信道中安全的交换密钥,用于加密后续的通信消息。 Whitfield Diffie 和 Martin Hellman 于 1976 提出该算法,之后被应用于安全领域,比如 Https 协议的 TSL(Transport Layer Security)以 DH 算法作为密钥交换算法。
session key 是主密钥,HTTPS 建立连接时使用 SSL/TLS 来交换 session key,后续的传输数据使用的是对称加密,对称加密的密钥就是通过 session key 生成的。
前面我们说过,session key 需要使用非对称加密的方式来传送,非对称加密算法常用的是 RSA,RSA 会把一方生成的 session key 用公钥加密后再发送给对方,可是,万一私钥泄露了,那 session key 也就泄露了。所以 SSL/TLS 使用另一种非对称加密算法来传送 session key,这种算法就是HD,RSA 是“传送”,而 HD 描述为“交换”更准确一些。
HD 的实现机制:
假设 A、B 通信
- A、B 相互通信一次,确定两个参数:整数 g 和 大素数 p,这个过程叫做“协商”
- A 生成隐私数据 a,a < p,计算得出 ga mod p,发送给 B
- B 生成隐私数据 b,b < p,计算得出 gb mod p,发送给 A
- A 计算 [(gb mod p)^a] %p,得到的结果是预备主密钥 session key
- B 计算 [(ga mod p)^b] %p,得到的结果是预备主密钥 session key
[(gb mod p)^a] %p = [(ga mod p)^b] %p = gab mod p,证明过程略,总之 A、B 生成的 session key 是相同的。
通信过程不加密,就是说 g、p、ga mod p、gb mod p 都是公开的,只有公开的这个四个数据,没有 a、b 就算不出来 gab mod p,这样就做到了 session key 由通信双方各自生成,不经过网络传递。
可以抽象的认为:A 的私钥是 a,公钥是 ga mod p;B 的私钥是 b,公钥是 gb mod p,gab mod p 是预备主密钥 session key,最终用于加密的密钥是会话密钥,通过 session key 和 H 派生出来,具体参看下面的 SSHv2 协议中的 DH
openssl 实现
使用生成因子(generator,即整数 g,2 或者 5,默认是 2)2 和随机的 1024-bit 的素数(大素数 p)产生 D0ffie-Hellman 参数,输出保存到参数文件 dh.param:
1 | [root@centos8 data]$openssl dhparam -out dh.param -2 1024 |
从 dh.param 中读取 Diffie-Hell 参数,以 C 代码的形式输出到 stdout:
1 | [root@centos8 data]$openssl dhparam -in dh.param -noout -C |
SSHv2 协议中的 DH
- Client 发送 e 值给 Server:
SSHv2 中 DH 的 p 和 g 固定,没有协商阶段
e = ga mod p
- Server 发送自身公钥、f、s 给 Client
f = gb mod p
H = hash(V_C||V_S||I_C||I_S||K_S||e||f||K)
类型 | 值 | 说明 |
---|---|---|
string | V_C | 客户端的初始报文(版本信息:SSH-2.0-xxx,不含结尾的 CR 和 LF) |
string | V_S | 服务器的初始报文 |
string | I_C | 客户端 SSH_MSG_KEX_INIT 的有效载荷(不含开头的数据长度值) |
string | I_S | 服务器的同上 |
string | K_S | 主机秘钥(dh gex reply(33)过程服务器发送 host key (RSA 公钥)) |
mpint | e | 客户端 DH 公钥 |
mpint | f | 服务器 DH 公钥 |
mpint | K | 共同 DH 计算结果 |
s 是用 Server 私钥对 H 加密后的结果
- Client 根据
/.ssh/known_hosts 文件,匹配 Server 公钥,如果/.ssh/known_hosts 文件中没有 Server 公钥,说明是第一次建立连接,需要用户手动允许保存 Server 公钥 - Client 计算预备主密钥 session key、同样的方式计算 H。使用 Server 公钥解密 s,解密得出的 H 和自己算出的 H 如果一样,则向 Server 发送 NEW KEYS,标志着密钥交换成功
- 自此,Client 和 Server 交换了 预备主密钥 K 和 H 值,通过前面协商的算法中的 密钥派生算法 导出各个业务的密钥:加密、MAC、IV,同时把 H 作为会话 ID(session ID)
TLS1.2 协议中的 DH
- Client 和 Server 相互协商版本号 和 各种算法,并互相发送随机数 和 session ID
- Server 发送数字证书给 Client
- Server 发送 Pubkey 值给 Client,数字签名用来确保 Pubkey 的合法完整
TLS1.2 中 DH 的 p 和 g 固定,没有协商阶段
Pubkey = gb mod p
- Client 校验证书的合法性;Client 计算出预备主密钥 session key;Client 发送 Pubkey 值给 Server
Pubkey = ga mod p
Server 计算出预备主密钥 session key;Server 发送 New Session Ticket、Change Cipher Spec、Encrypted Handshake Message 给 Client:
Encrypted Handshake Message:使用密钥加密的信息,目的一个是告诉服务端整个握手过程收到了什么数据,发送了什么数据,保证中间没人篡改报文,二是确认密钥的正确性,如果这个报文加解密校验成功,那么对称密钥就是正确的
Change Cipher Spec:编码改变通知,告知客户端,服务端已经切换到选定的加密套件(Cipher Suite),表示随后的信息都将用双方商定的加密方法和密钥发送。
New Session Ticket:这里有必要比较一下 session ticket 和 session ID 这两个角色:
如果出于某种原因,对话中断,就需要建立 SSL 连接,Client 在 client hello 阶段携带之前的 session ID,服务端确认 session ID 存在,双方就不再进行握手阶段剩余的步骤,而直接用已有的对话密钥进行加密通信,提高了重连的效率。
可是,session ID 往往只保留在一台服务器上(session ID 的存储及复用共享需要使用 redis 或 memcache 来实现),假如轮询到集群中其它服务器,无法识别该 session id,就无法恢复对话,session ticket 就是为了解决这个问题而诞生的,目前只有 Firefox 和 Chrome 浏览器支持。客户端不再发送 session ID,而是发送一个服务器在上一次对话中发送过来的 session ticket。这个 session ticket 是加密的,只有服务器才能解密,其中包括本次对话的主要信息,比如对话密钥和加密方法。当服务器收到 session ticket 以后,解密后就不必重新生成对话密钥了。
自此 Client 和 Server 都有了三个值:Client 随机数、Server 随机数、session key。这三个值可以计算出会话密钥,后面的通信使用会话密钥加密。