密钥交换算法
我们在使用对称加密算法的时候,我们用的是同一个密钥key
我们以AES加密为例,当我们需要加密明文,我们需要一个随机生成的key,作为密钥进行加解密,最后我们的问题是如何传递密钥
因为不给对方密钥,对方就不能解密,而直接传递密钥,就会被黑客监听,所以问题就变成了如何在不安全的信道上
如何安全的传输密钥
密钥算法也就是Diffie-Hellman算法,简称DH算法,他就是为了解决这个问题的,p=509,g=5,A=215,
你收到以后,他也选择一个随机数b,例如456,然后得到g的b次方,然后对p取余数,结果是181,然后计算S等于A的b次方,
除以p的余数,结果是121,乙把计算的B发给甲,甲计算S等于B的a次方,除以p的余数,结果计算的s相同,也是121,
所以最后双方协商的密钥,是121,要注意这个密钥并没有在网络上传输,通过网络传输的是p,g,A,B,但是通过这4个
数,黑客是无法推算出S的,所以更确切的说,DH算法是密钥协商算法,双方最终协商一个共同的密钥
我们把小a看成是甲的密钥,大A看成是甲的公钥,小b看成是乙的私钥,大b看成是公钥,DH的算法本质是各自生成自己的
公钥和私钥,然后交换公钥,并且根据自己的私钥和对方的公钥,生成最终的私钥,DH算法通过数学
package com.learn.securl;
import java.io.IOException;
import java.math.BigInteger;
import java.security.GeneralSecurityException;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
import javax.crypto.Cipher;
import javax.crypto.KeyAgreement;
import javax.crypto.SecretKey;
public class DH {
/**
* 我们写一个main方法来测试DH协商协议
* @param args
*/
public static void main(String[] args) {
// Bob 和 Alice
/**
* 首先我们创建两个Person对象
* 一个是Bob,一个是Alice
*/
Person bob = new Person("Bob");
Person alice = new Person("Alice");
// 各自生成KeyPair:
/**
* 然后Bob和Alice各自生成自己的KeyPair
*/
bob.generateKeyPair();
alice.generateKeyPair();
// 双方交换各自的PublicKey:
// Bob根据Alice的PublicKey生成自己的本地密钥:
/**
* 紧接着各自交换publicKey
* 由于Bob需要Alice的publicKey生成自己的本地密钥
* 所以我们对Bob调用generateSecretKey的时候
* 我们需要传入alice.publicKey.getEncoded()
*/
bob.generateSecretKey(alice.publicKey.getEncoded());
// Alice根据Bob的PublicKey生成自己的本地密钥:
/**
* 同样的Alice是通过bob的publicKey来生成自己的本地密钥
*/
alice.generateSecretKey(bob.publicKey.getEncoded());
// 检查双方本地密钥是否相同
/**
* 我们来看一下他们的本地密钥是否相同
* 如果双方的SecretKey是相同的
* 那么后序的通信就可以使用这个SecretKey
* AES加解密
* 我们看到Bob的privateKey和publicKey以及Bob生成的secretKey
* 我们再来看Alice的privateKey和publicKey以及Alice生成的secretKey
* 我们注意到Alice的SecretKey和Bob的SecretKey他们是相同的
* 所以在后序的通讯中,
* Bob和Alice可以使用这两个相同的key以及AES的加解密
*/
bob.printKeys();
alice.printKeys();
// 双方的SecretKey相同,后续通信将使用SecretKey作为密钥进行AES加密
String msgBobToAlice = bob.sendMessage("Hello, Alice!");
System.out.println("Bob -> Alice: " + msgBobToAlice);
String aliceDecrypted = alice.receiveMessage(msgBobToAlice);
System.out.println("Alice decrypted: " + aliceDecrypted);
}
}
/**
* 我们首先定义了一个Person类
* @author Leon.Sun
*
*/
class Person {
/**
* 我们定义了一个name字段,表示Person名字
*/
public final String name;
/**
* publicKey表示public Key
*/
public PublicKey publicKey;
/**
* privateKey表示private Key
*/
private PrivateKey privateKey;
/**
* 这个把表示最后生成的secretKey
*/
private SecretKey secretKey;
/**
* 我们定义个方法来初始化Person的名字
* @param name
*/
public Person(String name) {
this.name = name;
}
/**
* 生成本地KeyPair
*
* 紧接着我们定义一个generateKeyPair
*/
public void generateKeyPair() {
try {
/**
* 我们通过KeyPairGenerator.getInstance传入DH表示我们要生成KeyPairGenerator
* 用于DH算法
*/
KeyPairGenerator kpGen = KeyPairGenerator.getInstance("DH");
/**
* 表示我们要生成512位的keyPair
*/
kpGen.initialize(512);
/**
* 我们通过generateKeyPair所获得一个KeyPair对象
*/
KeyPair kp = kpGen.generateKeyPair();
/**
* 紧接着我们通过getPrivate和getPublic分别获得了privateKey和publicKey
*/
this.privateKey = kp.getPrivate();
this.publicKey = kp.getPublic();
} catch (GeneralSecurityException e) {
throw new RuntimeException(e);
}
}
/**
* 生成密钥
*
* 当通信的双方把publicKey交给双方的时候,
* 我们都可以从对方接收到public key这个字节中回复public key对象
*
* @param receivedPubKeyBytes
*/
public void generateSecretKey(byte[] receivedPubKeyBytes) {
try {
// 从byte[]恢复PublicKey
/**
* 我们通过X509EncodedKeySpec这个类传入收到的字节数组
* 就可以恢复公钥
*/
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(
receivedPubKeyBytes);
KeyFactory kf = KeyFactory.getInstance("DH");
/**
* 我们可以通过generatePublic
*/
PublicKey receivedPublicKey = kf.generatePublic(keySpec);
// 生成本地密钥:
/**
* 我们通过KeyAgreement.getInstance传入DH得到一个KeyAgreement对象
*/
KeyAgreement keyAgreement = KeyAgreement.getInstance("DH");
/**
* 然后我们通过init方法传入自己的private key
*/
keyAgreement.init(this.privateKey); // 自己的PrivateKey
/**
* 然后我们通过doPhase传入对方的publicKey,
*/
keyAgreement.doPhase(receivedPublicKey, true);// 对方的PublicKey
// 生成AES密钥:
/**
* 这个时候我们通过generateSecret传入AES表示我们要生成一个AES的密钥
* 这个SecretKey就是我们将来要用的密钥
*/
this.secretKey = keyAgreement.generateSecret("AES");
} catch (GeneralSecurityException e) {
throw new RuntimeException(e);
}
}
/**
* 打印密钥
*
* 我们用printKeys打印privateKey,publicKey,secretKey
*/
public void printKeys() {
System.out.printf("Name: %s\n", this.name);
System.out.printf("Private key: %x\n", new BigInteger(1,
this.privateKey.getEncoded()));
System.out.printf("Public key: %x\n",
new BigInteger(1, this.publicKey.getEncoded()));
System.out.printf("Secret key: %x\n",
new BigInteger(1, this.secretKey.getEncoded()));
}
/**
* 发送加密消息
*
* 我们用sendMessage方法用来测试AES加密
*
* @param message
* @return
*/
public String sendMessage(String message) {
try {
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, this.secretKey);
byte[] data = cipher.doFinal(message.getBytes("UTF-8"));
return Base64.getEncoder().encodeToString(data);
} catch (GeneralSecurityException | IOException e) {
throw new RuntimeException(e);
}
}
/**
* 接收加密消息并解密
*
* 我们用receiveMessage来测试收到的信息,
* 并且用AES解密
*
* @param message
* @return
*/
public String receiveMessage(String message) {
try {
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, this.secretKey);
byte[] data = cipher.doFinal(Base64.getDecoder().decode(message));
return new String(data, "UTF-8");
} catch (GeneralSecurityException | IOException e) {
throw new RuntimeException(e);
}
}
}
DH算法不能避免中间人攻击,如果黑客假冒乙和甲交互密钥,同时又假冒乙和甲交互密钥,他就可以成功的进行攻击
最后我们总结一下:
1. DH算法是一种密钥交换协议,通信双方通过一个不安全的信道协商密钥,然后进行对称加密传输
2. DH算法并没有解决中间人攻击的问题