【算法系列】实战篇:Diffie-Hellman算法实现通信秘钥流程

本文详细介绍了如何利用Diffie-Hellman算法在不安全的网络环境中安全地交换通信秘钥,包括算法原理、生成公私钥、交换公钥、计算通信秘钥以及加解密过程。在实际操作中,注意AES加解密时的Base64处理和可能遇到的Java安全问题。最后,建议对秘钥材料进行缓存管理。
摘要由CSDN通过智能技术生成

热门系列:


目录

热门系列:

  程序人生,精彩抢先看

1、Diffie-Hellman算法简介

2、使用DH算法生成通信秘钥加解密

        2.1 A向B请求获取秘钥组成参数

        2.2 生成A的公私钥材料

        2.3 A向B请求获取B的公钥材料

        2.4 通过B的公钥材料Y生成A的通信秘钥SecretKey

        2.5 加密请求参数

        2.6 解密请求参数

        2.7 全流程演示

3、总结


1、Diffie-Hellman算法简介

        Diffie-Hellman算法(以下简称为:DH算法),是最早的密钥交换算法之一,它使得通信的双方能在非安全的信道中安全的交换密钥,用于加密后续的通信消息。

        起基本流程原理如下:

假定小明小红期望在一个不安全的网络中协商一个共同的密钥,那么进行如下步骤:

  • 两人先说好大素数(质数)p和它的原始根g。

  • 小明随机产生一个数a,并计算A = p^a mod g, 发送给小红

  • 小红随机产生一个数b,并计算B = p^b mod g,发送给小明

此时, 小明手握小红发过来的B,结合自己产生的a开始这样计算:

B^a mod p = (p^b mod g)^a mod p = p^ab mod g

小红也拿到了小明发来的A,同时结合自己的b,也开始计算:

A^b mod p = (p^a mod g)^b mod p = p^ab mod g

这样小明小红都得到了相同的密钥。

流程图如下:


2、使用DH算法生成通信秘钥加解密

        以下会分为2端,发送端  A 和接收端  B ,后续以  A 和  B 简称描述。

        以下代码内常量参数,先贴出来:

private static final String KEY_ALGORITHM = "DH";
private static final String SECRET_ALGORITHM = "AES";
private static final int KEY_SIZE=512;
private static String ENCRYPT_ALGORITHM_MODE = "AES/CBC/PKCS5Padding";
private static String ENCRYPT_IV_PARAMETER_SPEC = "A-16-Byte-String";
//B的公钥材料
private static PublicKey receivePublicKey;
//B的私钥材料
private static PrivateKey receivePrivateKey;
//A的公钥材料
private static PublicKey senderPublicKey;
//A的私钥材料
private static PrivateKey senderPrivateKey;
//A的通信秘钥SecretKey
private static SecretKey senderSecretKey;
//B的通信秘钥SecretKey
private static SecretKey receiveSecretKey;

2.1 A向B请求获取秘钥组成参数

        该环节主要为了获取B的大素数(质数)p 和 它的原始根g,供后续使用。

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.Setter;


@Getter
@Setter
@AllArgsConstructor
public class DHEncryptParamBean {
    private String P;
    private String G;
    private int god;
}
/**
* @Description 从B获取大素数(质数)p 和 它的原始根g,并生成A的公私钥材料
* @return DHEncryptParamBean
**/
public static DHEncryptParamBean getParam() throws NoSuchAlgorithmException, InvalidKeySpecException {
	DHEncryptParamBean resultBean = new DHEncryptParamBean("0", "0", 0);

    KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(KEY_ALGORITHM);
    keyPairGenerator.initialize(KEY_SIZE);
    KeyPair keyPair = keyPairGenerator.generateKeyPair();
    receivePublicKey = keyPair.getPublic();
    receivePrivateKey = keyPair.getPrivate();
    byte[] encodedKey = receivePublicKey.getEncoded();
    X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(encodedKey);
    KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
    PublicKey publicKey = keyFactory.generatePublic(x509KeySpec);
    DHParameterSpec params = ((DHPublicKey) publicKey).getParams();
    
    resultBean.setP(params.getP().toString(10));
    resultBean.setG(params.getG().toString(10));
    resultBean.setGod(KEY_SIZE);
    return resultBean;

}

2.2 生成A的公私钥材料

/**
* @Description 生成的A的公私钥材料
* @return String
**/
public static void getPublicKey () throws NoSuchAlgorithmException{
     
    KeyPairGenerator senderPairGenerator = KeyPairGenerator.getInstance(KEY_ALGORITHM);
    senderPairGenerator.initialize(KEY_SIZE);
    KeyPair senderPair = senderPairGenerator.generateKeyPair();
     
    //生成的A的公私钥材料,后面会用于生成 加解密秘钥secretKey
	senderPublicKey = senderPair.getPublic();
    senderPrivateKey = senderPair.getPrivate();
}

2.3 A向B请求获取B的公钥材料

/**
* @Description 通过A的公钥材料,生成B的通信秘钥SecretKey
* @return byte []
**/
public static BigInteger getReceivePublicKey() {
    
    try {
        KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
        
        X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(senderPublicKey.getEncoded());
        DHPublicKey senderDHPublicKey = (DHPublicKey) keyFactory.generatePublic(x509KeySpec);
		
        //生成B公私钥
        KeyAgreement keyAgreement = KeyAgreement.getInstance(KEY_ALGORITHM);
        keyAgreement.init(receivePrivateKey);
        keyAgreement.doPhase(senderDHPublicKey, true);
        
        receiveSecretKey = keyAgreement.generateSecret(SECRET_ALGORITHM);
        BigInteger y = ((DHPublicKey)receivePublicKey).getY();
        
        return y;
    } catch (Exception e) {
    	e.printStackTrace();
        return null;
    }
    
}

2.4 通过B的公钥材料Y生成A的通信秘钥SecretKey

/**
* @Description 通过B的公钥材料Y,生成A的通信秘钥SecretKey
* @return
**/
public static void getSenderSecretKey(BigInteger revicePublicKeyY,DHEncryptParamBean paramBean){
	try {
		BigInteger p = new BigInteger(paramBean.getP(),10);
		BigInteger g = new BigInteger(paramBean.getG(),10);
		KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
		DHPublicKeySpec reviceDHPublicSpec = new DHPublicKeySpec(revicePublicKeyY, p, g);
        DHPublicKey reviceDHPublicKey = (DHPublicKey) keyFactory.generatePublic(reviceDHPublicSpec);
		
		KeyAgreement keyAgreement = KeyAgreement.getInstance(KEY_ALGORITHM);
		keyAgreement.init(senderPrivateKey);
		keyAgreement.doPhase(reviceDHPublicKey, true);
		
		senderSecretKey = keyAgreement.generateSecret(SECRET_ALGORITHM);
		
	} catch (NoSuchAlgorithmException e) {
		e.printStackTrace();
	} catch (InvalidKeyException e) {
		e.printStackTrace();
	} catch (InvalidKeySpecException e) {
		e.printStackTrace();
	}

}

2.5 加密请求参数

/**
* @Description 加密请求参数
* @Param [secretKey]
* @return java.lang.String
**/	
public static String encrptyParam(SecretKey secretKey){

    try {
        //加密发送端请求参数,通过AES加密
        Cipher aesCipher = Cipher.getInstance(ENCRYPT_ALGORITHM_MODE);
        aesCipher.init(Cipher.ENCRYPT_MODE, secretKey, new IvParameterSpec(ENCRYPT_IV_PARAMETER_SPEC.getBytes(StandardCharsets.UTF_8)));

        //请求参数原文
        Map<String,Object> paramMap = new HashMap<>();
        paramMap.put("name","jack");

        //加密参数
        String originalBody = JSON.toJSONString(paramMap);
        byte[] encryptBody = aesCipher.doFinal(originalBody.getBytes(StandardCharsets.UTF_8));
        byte[] base64Body = Base64.getEncoder().encode(encryptBody);
        String encryptParam = new String(base64Body, StandardCharsets.UTF_8);

        System.out.println("加密后数据:"+encryptParam);
        return encryptParam;
    } catch (NoSuchAlgorithmException e) {
        e.printStackTrace();
    } catch (InvalidKeyException e) {
        e.printStackTrace();
    } catch (InvalidAlgorithmParameterException e) {
        e.printStackTrace();
    } catch (NoSuchPaddingException e) {
        e.printStackTrace();
    } catch (BadPaddingException e) {
        e.printStackTrace();
    } catch (IllegalBlockSizeException e) {
        e.printStackTrace();
    }

    return null;
}

2.6 解密请求参数

/**
* @Description 解密请求参数
* @Param [secretKey]
* @return java.lang.String
**/	
private static String decryptParam(SecretKey secretKey,String encryptParam){
    try {
        Cipher deAesCipher = Cipher.getInstance(ENCRYPT_ALGORITHM_MODE);
        deAesCipher.init(Cipher.DECRYPT_MODE, secretKey, new IvParameterSpec(ENCRYPT_IV_PARAMETER_SPEC.getBytes(StandardCharsets.UTF_8)));

		byte[] decodedBase64Body = Base64.getDecoder().decode(encryptParam.getBytes(StandardCharsets.UTF_8));
        byte[] realBody = deAesCipher.doFinal(decodedBase64Body);

        String deStr = new String(realBody, StandardCharsets.UTF_8);
        System.out.println("解密后数据:"+deStr);
        return deStr;
    } catch (NoSuchAlgorithmException e) {
        e.printStackTrace();
    } catch (NoSuchPaddingException e) {
        e.printStackTrace();
    } catch (InvalidKeyException e) {
        e.printStackTrace();
    } catch (InvalidAlgorithmParameterException e) {
        e.printStackTrace();
    } catch (IllegalBlockSizeException e) {
        e.printStackTrace();
    } catch (BadPaddingException e) {
        e.printStackTrace();
    }

    return null;
}

2.7 全流程演示

public static void main(String[] args) {
	try {
		//2.1 从B获取大素数(质数)p 和 它的原始根g,并生成B的公私钥材料
		DHEncryptParamBean paramBean = getParam();
		
		//2.2 获取A的公私钥材料
		getPublicKey();
		
		//2.3 通过A的公钥材料,生成B的通信秘钥SecretKey
		BigInteger receivePublicKeyY = getReceivePublicKey();
		
		//2.4 通过B的公钥材料Y,生成A的通信秘钥SecretKey
		getSenderSecretKey(receivePublicKeyY,paramBean);
		
		//2.5 A使用获取的SecretKey加密参数
		String encryptParam = encrptyParam(senderSecretKey);
		
		//2.6 B使用获取的SecretKey解密参数
		String decryptParam = decryptParam(receiveSecretKey,encryptParam);

	} catch (NoSuchAlgorithmException e) {
		e.printStackTrace();
	} catch (InvalidKeySpecException e) {
		e.printStackTrace();
	}
}

        请求完成结果如下:


3、总结

        此实践内容中,有几个需要注意的点:

        1、使用AES加解密时,需要将加解密的内容通过Base64先解密/后加密处理,关键代码如下:

//加密参数
String originalBody = JSON.toJSONString(paramMap);
byte[] encryptBody = aesCipher.doFinal(originalBody.getBytes(StandardCharsets.UTF_8));
//这一步Base64加密要有,否则会报错
byte[] base64Body = Base64.getEncoder().encode(encryptBody);
String encryptParam = new String(base64Body, StandardCharsets.UTF_8);



//解密参数
//对应上一步加密中的Base64解密,必须有否则会报错
byte[] decodedBase64Body = Base64.getDecoder().decode(encryptParam.getBytes(StandardCharsets.UTF_8));
byte[] realBody = deAesCipher.doFinal(decodedBase64Body);
String deStr = new String(realBody, StandardCharsets.UTF_8);

        否则,会报出如下错误:

        javax.crypto.IllegalBlockSizeException: Input length must be multiple of 16 when decrypting with padded cipher

         

        2、keyAgreement.generateSecret(SECRET_ALGORITHM)这句代码可能会报错,报错内容如下:

        java.security.NoSuchAlgorithmException: Unsupported secret key algorithm: AES

         可在运行时,在JVM参数上,加上:-Djdk.crypto.KeyAgreement.legacyKDF=true ,即可解决!

         当然,还有很多其他的解决方式。例如,我接来下用的一种,比如在获取SecretKey是做如下处理:

byte[] secretKey = keyAgreement.generateSecret();
BigInteger secretBig = new BigInteger(1, secretKey);
byte[] secretTmpByte = secretBig.toString(10).getBytes(StandardCharsets.UTF_8);
secretKey = Arrays.copyOfRange(secretTmpByte, 0, 32);
//如果此处获取通信秘钥如此处理,那B也需要以同样方式处理,才可保证通讯秘钥一致,即receiveSecretKey
senderSecretKey = new SecretKeySpec(secretKey, SECRET_ALGORITHM);

        另外,在网上还有这种方式:

直接代码中配置
System.getProperties().setProperty("jdk.crypto.KeyAgreement.legacyKDF", "true");

        至此,DH加密梳理演示,就算完成啦。最后,有一点建议,就是请求过程中的这些秘钥材料、SecretKey都可以使用一个缓存存储起来使用,定制一个失效时间。

        如有不足,也欢迎各位朋友下方留言探讨~~~~~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

善良勤劳勇敢而又聪明的老杨

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值