目录
- Diffie-Hellman的流程原理
- 流程原理在Java中的对应
- Java应用代码
- JDK源码中封装的原理细节
- 生成密钥对
- 通过对方公钥和自己的私钥生成相同的对称密钥
本文的思路:
- 先了解Diffie-Hellman的流程原理,然后将其流程和Java的实现对应起来;理解了原理和Java实现的流程,再写应用代码进一步辅助验证;最后走一走源码流程中的相关细节,作最终验证。
- 文末了解一下性能更好、安全性更高的ECDH(基于椭圆曲线来实现的Diffie-Hellman)
一、Diffie-Hellman的流程原理
DH的流程中涉及三个角色,主要是通信双方Alice、Bobby,和可能存在窃听的中间人Eaves。
场景:Alice要和Bobby通信,发送的信息可能会被中间人Eaves窃听到,Alice和Bobby如何通过DH算法来保证通信的机密性?
DH是密钥协商(key agreement)算法,通信双方并没有真正的交换密钥,而是通过交换部分可以公开的信息来计算生成出相同的对称密钥,然后通过对称密钥对消息进行加密,从而保证消息的机密性。
DH的流程原理如下:
- 1.发送方Alice:
- 选择两个质数P和G——大质数P和生成元G
- 然后生成一个随机数A作为自己的私钥
- 接着计算(G^A)modP作为自己的公钥
- 最后把P、G和公钥(G^A)modP通过可能被监听的网络发送给接收方Bobby
- 注意:私钥A是不公开、不发送的,Alice自己保留
- 2.接收方Bobby:
- 收到Alice的消息后,首先选择一个随机数B作为自己的私钥
- 然后计算(G^B)modP作为自己的公钥
- 最后Bobby将自己的公钥(G^B)modP发送给Alice
- 注意:私钥B是不公开、不发送的,Bobby自己保留
- 3.双方计算出相同的对称密钥
- Alice通过自己的私钥A和对方的公钥(G^B)modP计算出相同的对称密钥:((G^BmodP)^A)modP,即(G^(B*A))modP
- Bobby通过自己的私钥B和对方的公钥(G^A)modP计算出相同的对称密钥:((G^AmodP)^B)modP,即(G^(A*B))modP
- 4.最后算双方通过计算出的、相同的对称密钥进行加密通信
再做进一步的抽象,四小步骤实际上可以抽象成两个阶段:
- 第一阶段(第1~3步):通过DH算法进行密钥协商,协商出对称密钥
- 第二阶段(第4步):通过协商出的对称密钥进行加密通信
我们分析一下在Alice、Bobby协商密钥的过程中,中间人Eaves通过窃听到的信息能否计算出密钥:
- Alice拥有的信息:大质数P、生成元G、自己的公钥(G^A)modP和私钥A、对方Bobby的公钥(G^B)modP,然后通过自己的私钥和对方的公钥计算出对称密钥
- Bobby拥有的信息:大质数P、生成元G、自己的公钥(G^B)modP和私钥B、对方Alice的公钥(G^A)modP,然后通过自己的私钥和对方的公钥计算出对称密钥
- Eaves能窃听到的公开信息:大质数P、生成元G、Alice的公钥(G^A)modP、Bobby的公钥公钥(G^B)modP,因为Eaves没有私钥A或B,所以无法计算出密钥
二、流程原理在Java中的对应
JDK的API封装了很多细节(大质数P、生成元G、私钥A/B,公钥(G^A)modP/(G^B)modP都封装到了PublicKey或PrivateKey对象中),对外程序员能看到的类就是公钥、私钥,DH流程原理的细节对应放在第四部分再剖析,这一部分只做大概的、整体上的流程对应,目的是为了顺利写出第三部分的应用代码。
1.密钥协商阶段,这一部分主要是DH算法:
- Alice通过KeyPairGenerator生成自己的密钥对,保留私钥PrivateKey,然后将公钥PublicKey发送给Bobby,通过网络一般不直接发送Java对象,而是发送公钥的字节数组;
- Bobby收到Alice发过来的公钥数组:
- 先通过X509EncodedKeySpec解析公钥规范,然后通过KeyFactory还原出公钥对象PublicKey(实际上公钥对象包含了大质数P、生成元G和Alice的公钥Y)
- 接着通过还原出的公钥对象生成自己的密钥对
- 然后将自己的公钥数组回送给Alice
- 最后,Alice和Bobby都通过自己的私钥和对方的公钥经由KeyAgreement类计算出相同的共享密钥SecretKey(这里要重点关注)
2.加密通信阶段,这一部分主要是对称加密算法,本文选择现役、流行的AES算法为例:
- 通过Cipher.getInstance工厂方法拿到加解密对象
- 设置加密的参数:cipher_mode、密钥协商阶段得到的SecretKey、分组密码分组的模式所需的初始化向量IvParameterSpec
- doFinal完成加解密
三、应用代码
我们按照第二部分中的流程来。
1.Alice生成密钥对
// 密钥协商算法密钥对生成入参,查看KeyPairGenerator.getInstance文档
private static String DH_KEY = "DH";
/***
* Alice生成密钥对
* @return Alice的密钥对
* @throws Exception
*/
public static KeyPair generateKeyPair() throws Exception {
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(DH_KEY);
return keyPairGenerator.generateKeyPair();
}
代码很简洁,几乎都是固定格式,你需要关注的是KeyPairGenerator.getInstance方法的入参,这个字符串从哪里去找?
JDK中都在一个页面找,后面的getInstance都在相同的页面,只是要在页面找不同的类的文档,页面地址:Java Security Standard Algorithm Names
找到KeyPairGenerator类的文档,截图如下:
2.Bobby根据Alice的公钥生成自己的密钥对
/**
* Bobby收到Alice的公钥数组:
* 1.先将公钥数组解析成公钥对象
* KeyFactory.getInstance的入参查看KeyFactory的文档
* 文档地址:https://docs.oracle.com/en/java/javase/15/docs/specs/security/standard-names.html
* 2.根据Alice的公钥对象生成自己的密钥对
* @param publicKeyArray 收到的、对方的公钥数组
* @return
* @throws Exception
*/
public static KeyPair generateKeyPair(byte[] publicKeyArray) throws Exception {
// 将Alice发过来的公钥数组解析成公钥对象
X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(publicKeyArray);
KeyFactory keyFactory = KeyFactory.getInstance(DH_KEY);
PublicKey publicKey = keyFactory.generatePublic(x509EncodedKeySpec);
// Bobby根据Alice的公钥生成自己的密钥对
DHParameterSpec dhParameterSpec = ((DHPublicKey)publicKey).getParams();
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(DH_KEY);
keyPairGenerator.initialize(dhParameterSpec);
return keyPairGenerator.generateK