目录
概述
我们在使用AES算法的过程中,使用了相同的密钥,那么如果在实际应用中: 在网络上的两个用户之间需要传输文件时,A使用自己的密钥向B发送了加密文件,那么B就需要A的密钥来解密文件。
那么这就会出现安全隐患:
在不安全的信道上A发送的加密文件被黑客拿到了,但是由于是加密过的,所以没有什么用。但是如果被黑客拿到了密钥呢?
要解决这个问题,就需要使用密钥交换算法: DH算法(Diffie-Hellman算法)。DH算法解决了在双方不直接传递密钥的情况下完成密钥交换。
数学原理
下面是DH算法交换密钥的步骤。假设Alice,Bob双方需要传递密钥,他们可以这样:
- Alice首选选择一个素数p= 509,底数g = 5(任选),随机数a = 123,然后计算A = g^a mod p,结果是215,然后,Alice发送p = 509,g=5,A=215给Bob;
- Bob收到后,也选择一个随机数b=456,然后计算B = g^b mod p,结果是181,Bob再同时计算K = A^b mod p,结果是121;
- Bob把计算的B=181发给Alice,Alice计算K = B^a mod p的余数,计算结果与Bob算出的结果一样,都是121。
所以最终双方协商出的密钥K是121。注意到这个密钥K并没有在网络上传输。而通过网络传输的p,g,A和B是无法推算出K的,因为实际算法选择的素数是非常大的。所以,更确切地说,DH算法是一个密钥协商算法,双方最终协商出一个共同的密钥,而这个密钥不会通过网络传输。
如果我们把a看成甲的私钥,A看成甲的公钥,b看成乙的私钥,B看成乙的公钥,DH算法的本质就是双方各自生成自己的私钥和公钥,私钥仅对自己可见,然后交换公钥,并根据自己的私钥和对方的公钥,生成最终的密钥secretKey,DH算法通过数学定律保证了双方各自计算出的secretKey是相同的。
实现案例
用户类
用户类用于模拟收发文件的双方,以及具有生成各自的公钥私钥,计算共享密钥的方法。
生成公私钥对的方法:
- 创建基于DH算法的密钥对生成器;
- 初始化长度;
- 通过密钥生成器生成密钥对;
- getPrivate()获取私钥,getPublic()获取公钥。
// 生成本地KeyPair:(公钥+私钥)
public void generateKeyPair() {
try {
// 创建DH算法的“秘钥对”生成器
KeyPairGenerator kpGen = KeyPairGenerator.getInstance("DH");
kpGen.initialize(512);
// 生成一个"密钥对"
KeyPair kp = kpGen.generateKeyPair();
this.privateKey = kp.getPrivate(); // 私钥
this.publicKey = kp.getPublic(); // 公钥
} catch (GeneralSecurityException e) {
throw new RuntimeException(e);
}
}
根据对方的公钥计算共享密钥:
- 根据字节数组恢复密钥对象X509EncodedKeySpec;
- 创建基于DH算法的keyFactory;
- 通过keyFatcory将密钥对象加工成为PublicKey对象;
- 创建基于DH算法的密钥协议对象;
- 通过密钥协议对象先把自己的PrivateKey初始化进去;
- 调用密钥协议对象的doPhase()方法,根据对方的PublicKey;
- 最后调用generateSecret()方法算出共享密钥。
完整的Person类:
// 用户类
class Person {
public final String name; // 姓名
// 密钥
public PublicKey publicKey; // 公钥
private PrivateKey privateKey; // 私钥
private byte[] secretKey; // 本地秘钥(共享密钥)
// 构造方法
public Person(String name) {
this.name = name;
}
// 生成本地KeyPair:(公钥+私钥)
public void generateKeyPair() {
try {
// 创建DH算法的“秘钥对”生成器
KeyPairGenerator kpGen = KeyPairGenerator.getInstance("DH");
kpGen.initialize(512);
// 生成一个"密钥对"
KeyPair kp = kpGen.generateKeyPair();
this.privateKey = kp.getPrivate(); // 私钥
this.publicKey = kp.getPublic(); // 公钥
} catch (GeneralSecurityException e) {
throw new RuntimeException(e);
}
}
// 按照 "对方的公钥" => 生成"共享密钥"
public void generateSecretKey(byte[] receivedPubKeyBytes) {
try {
// 从byte[]恢复PublicKey:
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(receivedPubKeyBytes);
// 根据DH算法获取KeyFactory
KeyFactory kf = KeyFactory.getInstance("DH");
// 通过KeyFactory创建公钥
PublicKey receivedPublicKey = kf.generatePublic(keySpec);
// 创建秘钥协议对象(用于秘钥协商)
KeyAgreement keyAgreement = KeyAgreement.getInstance("DH");
keyAgreement.init(this.privateKey); // 初始化"自己的PrivateKey"
keyAgreement.doPhase(receivedPublicKey, true); // 根据"对方的PublicKey"
// 生成SecretKey本地密钥(共享公钥)
this.secretKey = keyAgreement.generateSecret();
} catch (GeneralSecurityException e) {
throw new RuntimeException(e);
}
}
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));
}
}
测试类
双方各自通过对方的公钥计算自己的共享密钥,最后打印输出共享密钥,验证一致性。
public class Main {
public static void main(String[] args) {
// Bob和Alice:
Person bob = new Person("Bob");
Person alice = new Person("Alice");
// 各自生成KeyPair: 公钥+私钥
bob.generateKeyPair();
alice.generateKeyPair();
// 双方交换各自的PublicKey(公钥):
// Bob根据Alice的PublicKey生成自己的本地密钥(共享公钥):
bob.generateSecretKey(alice.publicKey.getEncoded());
// Alice根据Bob的PublicKey生成自己的本地密钥(共享公钥):
alice.generateSecretKey(bob.publicKey.getEncoded());
// 检查双方的本地密钥是否相同:
bob.printKeys();
alice.printKeys();
// 双方的SecretKey相同,后续通信将使用SecretKey作为密钥进行AES加解密...
}
}
总结
- DH算法是一种密钥交换协议,通信双方通过不安全的信道协商密钥,然后进行对称加密传输。
- 使用DH算法协商的双方,通过对方的公钥和自己的私钥计算出相等的共享密钥。
- 计算后的共享密钥双方相等,所以无需再传输共享密钥,就可以使用共享密钥进行安全的对称加密传输。