密钥协商算法Diffie-Hellman的Java实现

目录

  • 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. 第一阶段(第1~3步):通过DH算法进行密钥协商,协商出对称密钥
  2. 第二阶段(第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
  • 13
    点赞
  • 42
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
Diffie-Hellman算法是一种交换协议,用于在不安全的通信渠道上安全地交换Java中可以使用javax.crypto包中的KeyAgreement类来实现Diffie-Hellman算法。具体步骤如下: 1. 创建KeyPairGenerator对象,指定算法为DiffieHellman。 2. 生成对,包括公和私。 3. 创建KeyAgreement对象,指定算法为DiffieHellman。 4. 初始化KeyAgreement对象,传入自己的私。 5. 使用对方的公,执行KeyAgreement对象的doPhase方法,生成共享。 6. 将共享用于加通信。 需要注意的是,Diffie-Hellman算法只能用于交换,不能用于加和解数据。在实际应用中,通常会使用共享来加数据,例如使用AES算法。 示例代码如下: ```java import javax.crypto.KeyAgreement; import javax.crypto.KeyGenerator; import javax.crypto.SecretKey; import java.security.KeyPair; import java.security.KeyPairGenerator; import java.security.NoSuchAlgorithmException; public class DiffieHellmanExample { public static void main(String[] args) throws NoSuchAlgorithmException { // 创建KeyPairGenerator对象,指定算法为DiffieHellman KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("DiffieHellman"); // 生成对,包括公和私 KeyPair keyPair = keyPairGenerator.generateKeyPair(); byte[] publicKey = keyPair.getPublic().getEncoded(); byte[] privateKey = keyPair.getPrivate().getEncoded(); // 创建KeyAgreement对象,指定算法为DiffieHellman KeyAgreement keyAgreement = KeyAgreement.getInstance("DiffieHellman"); // 初始化KeyAgreement对象,传入自己的私 keyAgreement.init(keyPair.getPrivate()); // 假设对方的公为publicKey2 byte[] publicKey2 = ...; // 使用对方的公,执行KeyAgreement对象的doPhase方法,生成共享 keyAgreement.doPhase(publicKey2, true); SecretKey sharedSecretKey = keyAgreement.generateSecret("AES"); // 将共享用于加通信 byte[] plaintext = "Hello, world!".getBytes(); byte[] ciphertext = encrypt(plaintext, sharedSecretKey); byte[] decryptedPlaintext = decrypt(ciphertext, sharedSecretKey); System.out.println(new String(decryptedPlaintext)); } private static byte[] encrypt(byte[] plaintext, SecretKey key) { // 使用共享key加plaintext return null; } private static byte[] decrypt(byte[] ciphertext, SecretKey key) { // 使用共享key解ciphertext return null; } } ```
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值